BTS FED
ACTIVITÉ SON
Start
A game created by TON PROF PRÉFÉRÉ
BTS FED
pRÊTS à DÉCOUVRIR LA NATURE DU SON ?
Missions
A game created by TON PROF PRÉFÉRÉ
I am a cool subtitle, perfect for providing more context about the topic you are going to address
ACTIVITÉ SON
Quel est ton prénom ?
suivant
§classe§: *2
C'est parti !
Quelle est ta classe ?
§nom§: *3
* ATTENTION : ce prénom sera écrit sur ton diplôme à la fin
SON
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
SPÉCIALISTE SON
I LE SON
?$S1|=|1
II INTENSITÉ SONORE
?$S2|=|1
?$S1|=|1&S2|=|1
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
EXERCICES
I am a cool subtitle, perfect for providing more context about the topic you are going to address
SPÉCIALISTE SON
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
§nom§
ANNEE
I LE SON
16,96ms
10 périodes
f =
T =
Hz
x 10 s
ms =
VALIDER
SON PUR
ANALYSER CES SONS ET RANGER LES DANS LA BONNE CATÉGORIE
$S1|=|1
SON COMPLEXE
BRUIT
II INTENSITÉ SONORE
Le bruit devient dangereux pour l'audition à partir de 85 décibels (dB), surtout en cas d'exposition prolongée. Ce seuil est reconnu par l'Organisation mondiale de la santé (OMS) et les autorités sanitaires comme le niveau à ne pas dépasser sur une journée de travail de 8 heures.
Calculer le niveau sonore L d'une perceuse dont l’intensité est de 3,5.10-4W.m-2
$S2|=|1
L= dB
Ce son est-il potentiellement dangereux ?
VALIDER
missions
exercices
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
son pur vs son complexe
définition
étude d'un son
étude d'un source sonore
?$son4|=|1
?$son2|=|1
?$son3|=|1
?$son1|=|1
Déplacer les mots pour compléter les phrases ci-dessous :
PÉRIODE
VIBRATION
PUR
NIVEAU
BRUIT
INTENSITÉ
FRÉQUENCE
MÉCANIQUE
120 dB
COMPLEXE
90 dB
Le son est une onde ______ longitudinale qui se propage sous forme de __________ . La ____ d’un son correspond au nombre de vibrations par seconde, elle s’exprime en Hertz. La ______ correspond au temps d’une vibration complète ; elle s’exprime en seconde. L’ _________ sonore s’exprime en W.m² et mesure la puissance transportée par le son par unité de surface. Pour comparer les sons, on utilise le _________ sonore, exprimé en décibel. Un son devient dangereux pour l’audition à partir de ______ dB pour une exposition prolongée, et il peut être immédiatement dangereux au‑delà de ______ dB. Un son ____ est un son composé d’une seule fréquence. Un son __________ est formé de plusieurs fréquences superposées. Le _______ est un son dont les variations sont irrégulières et désagréables.
$son1|=|1
Complétez le tableau
t (s)
t (s)
$son2|=|1
t (s)
BRUIT
SON PUR
SON COMPLEXE
Complétez le tableau
t (s)
$son2|=|1
t (s)
t (s)
BRUIT
SON COMPLEXE
SON PUR
L’enregistrement d’une onde sonore est représenté sur ce graphique.Quelles sont les caractéristiques de ce son ?
Étude d'un son
$son3|=|1
T =
f =
Hz
nature * =
VALIDER
nature * = simple/composé/ bruit
Étude d'une source sonore
Une source sonore a une intensité I1 = 7,2.10-3 W.m-2 à une distance R1 = 10 m de cette source.1. Calculer le niveau sonore L12. Déterminer la puissance P de la source, sachant qu’elle émet dans toutes les directions de l’espace. 3. Dire si cette source présente un danger
$son4|=|1
L =
dB
P =
Cette source est-elle dangereuse ? =
VALIDER
Mission 1
Left or right?
Let's start with something simple: answer the questions by choosing the correct image. Good luck!
Start
mission 1
Hint: A great title
1/3
How much information does our brain retain through visual stimuli such as images, interactivities,or animations?
mission 1
Hint: A great title
2/3
What would you use in your presentation to entertain, provide relevant information, and capture the attention ofyour audience?
mission 1
Hint: A great title
3/3
What percentage of our brain is involved in processing visual stimuli, such as images, interactivities, or animation?
Mission 1 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 2
Mission 3
Mission 4
Mission 5
COMPLETE!
Mission 2
Which one doesn't fit?
Now come three demanding tests! You will see in each of them an instrument that DOES NOT FIT for some reason. Which one?
Start
mission 2
1/3
Hint: A great title
mission 2
2/3
Hint: A great title
mission 2
3/3
Hint: A great title
Mission 2 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Recover all band members!
Mission 3
Mission 4
Mission 5
COMPLETE!
COMPLETE!
mission 3
IDENTIFY THE letter
Do you have a good memory? Choose the missing word correctly to complete the lyrics of these famous compositions.
Start
mission 3
1/3
Hint: A great title
Did you know that ________ allows you to share your creation directly, without the need for downloads?
Enter an incorrect
Enter the correct answer ✔
Enter an incorrect
mission 3
2/3
Hint: A great title
Did you know that images illustrate what you want to convey and are a _____ to add additional info?
Write here an incorrect answer
Write here an incorrect answer
Write here the correct answer ✔
mission 3
3/3
Hint: A cool title
Did you know that ________ are an aesthetic resource that tell stories by themselves and also keep the brain awake?
Write here an incorrect answer
Write here the correct answer ✔
Write here an answer incorrect
Mission 3 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 4
Mission 5
Complete!
Complete!
Complete!
mission 4
What musical notes are these?
Next you will see three staves with several notes in treble clef. Can you recognize them?
Start
mission 4
1/3
Hint: A great title
Re Sol La Mi
Mi Sol Re Do
Fa La Si Sol ✔
mission 4
2/3
Hint: A great title
Do Re Si Mi
Re Mi Do Fa ✔
Re Sol Mi Fa
mission 4
3/3
Hint: A great title
Do Mi Re Si
Do Sol Fa Si ✔
Do Fa Re La
Mission 4 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 5
Complete!
Complete!
Complete!
Complete!
mission 5
ANSWER CORRECTLY
And finally, a quick general knowledge test. If you fail, get ready to receive a tomato splash!
Start
mission 5
1/3
Hint: A great title
Did you know that multimedia content is essential to achieve a WOW effect in your creations?
Write here an incorrect answer
Write here the correct answer ✔
Write here an answer incorrect
mission 5
Hint: A great title
2/3
It is clear that, when making purchases, the emotional component is a driving force, but what is the exact percentage of purchases we make thanks to emotions?
Enter the correct answer ✔
Enter an incorrect answer
Enter an incorrect answer
mission 5
3/3
Hint: A great title
What percentage of our brain is involved in processing visual stimuli, like images, interactivities, or animation?
Write here an incorrect answer
Write here an incorrect answer
Write here the correct answer ✔
Mission 5 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances its understanding.
Continue
THE BAND IS COMPLETE
That sounds great! 😊
Here you can include a text to congratulateand wish luck to your audience at the end.
Boo!
Here you can include a great message indicating to the audience that they have not guessed the answer
Try again!
<div style="width: 100%; max-width: 550px; background: #b22222; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #8b0000;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnWind" style="padding:10px; background:#1abc9c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🌬️ VENT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.2); padding:15px; border-radius:10px;">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'wind') {
const bufferSize = 2 * audioCtx.sampleRate, buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate), output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer; source.loop = true;
const filter = audioCtx.createBiquadFilter();
lfo = audioCtx.createOscillator();
const lfoGain = audioCtx.createGain();
filter.type = "lowpass"; filter.frequency.value = 400; filter.Q.value = 10;
lfo.frequency.value = 0.2; lfoGain.gain.value = 300;
lfo.connect(lfoGain); lfoGain.connect(filter.frequency);
source.connect(filter); filter.connect(ana);
lfo.start(); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3 : 8;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (['piano', 'diapason', 'wind'].includes(mode)) ? 'none' : 'block';
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
updateMode('piano');
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let y = (dataWave[i] / 256) * cW.height;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'wind') {
for(let i=0; i<cF.width; i+=4) {
let h = (Math.random() * 40 + 5) * (1 - i/cF.width);
ctxF.fillRect(i, cF.height - h - 10, 2, h);
}
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let posX = p * i;
if (posX < cF.width) ctxF.fillRect(posX, cF.height - (60/i) - 10, 3, 60/i);
}
} else if (currentType === 'diapason') {
let pD = (440 / 1500) * cF.width;
ctxF.fillRect(pD, cF.height - 70, 4, 60);
} else {
ctxF.fillRect(p, cF.height - 70, 4, 60);
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3 : 8;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block';
if (mode === 'diapason') currentFreq = 440;
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, 'piano');
draw();
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
let rms = 0;
for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128);
let hFactor = (rms / dataWave.length) * 15;
// SEUIL DE SÉCURITÉ : Si le son est trop faible, on n'affiche rien
if (hFactor < 0.05) hFactor = 0;
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let y = ( (dataWave[index]-128) * Math.min(1, hFactor) + 128 ) / 256 * cW.height;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
if (hFactor > 0) { // On ne dessine le spectre que s'il y a du son
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
let h = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - h - 10, 2, h);
}
} else {
let p = (currentFreq / 1500) * cF.width;
let hBase = Math.min(80, hFactor * 80);
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, cF.height - hBase - 10, 4, hBase);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let hHarm = hBase / i;
ctxF.fillRect(p*i, cF.height - hHarm - 10, 3, hHarm);
}
} else {
ctxF.fillRect(p, cF.height - hBase - 10, 3, hBase);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hBase/2) - 10, 2, hBase/2); ctxF.globalAlpha = 1;
}
}
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #2c3e50; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #34495e;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #626e6f;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#e74c3c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px;">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate);
const out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer; source.loop = true;
const filter = audioCtx.createBiquadFilter();
filter.type = "bandpass";
filter.frequency.value = 800;
filter.Q.value = 0.7;
lfo = audioCtx.createOscillator();
const lfoGain = audioCtx.createGain();
lfo.frequency.value = 0.6;
lfoGain.gain.value = 400;
lfo.connect(lfoGain); lfoGain.connect(filter.frequency);
source.connect(filter); filter.connect(ana);
lfo.start(); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3 : 8;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block';
if (mode === 'diapason') currentFreq = 440;
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
updateMode('piano');
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
// Oscilloscope
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let y = (dataWave[i] / 256) * cW.height;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
// Spectre corrigé
ctxF.fillStyle = "#ffeb3b";
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
let dist = Math.abs(i - cF.width/3);
let h = (Math.random() * 50 + 10) * Math.max(0.1, 1 - dist/(cF.width/1.5));
ctxF.fillRect(i, cF.height - h - 10, 2, h);
}
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let hHarm = 60 / i;
let posX = p * i;
if (posX < cF.width) ctxF.fillRect(posX, cF.height - hHarm - 10, 3, hHarm);
}
} else {
ctxF.fillRect(p, cF.height - 70, 4, 60);
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #2c3e50; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #34495e;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #626e6f;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#e74c3c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px;">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0;
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
// Bruit de base
const bSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate);
const out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer;
source.loop = true;
// FILTRE : Pour simuler la voix humaine (Bande passante)
const filter = audioCtx.createBiquadFilter();
filter.type = "bandpass";
filter.frequency.value = 800;
filter.Q.value = 0.7;
// LFO : Pour créer des fluctuations (effet de murmure)
lfo = audioCtx.createOscillator();
const lfoGain = audioCtx.createGain();
lfo.frequency.value = 0.6;
lfoGain.gain.value = 400;
lfo.connect(lfoGain);
lfoGain.connect(filter.frequency);
source.connect(filter);
filter.connect(ana);
lfo.start();
source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3 : 8;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block';
if (mode === 'diapason') currentFreq = 440;
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
updateMode('piano');
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
// Oscilloscope
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let y = (dataWave[i] / 256) * cW.height;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
// Spectre
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
let dist = Math.abs(i - cF.width/3); // Concentration sur les médiums
let h = (Math.random() * 50 + 10) * Math.max(0.1, 1 - dist/(cF.width/1.5));
ctxF.fillRect(i, cF.height - h - 10, 2, h);
}
} else {
let p = (currentFreq / 1500) * cF.width;
ctxF.fillRect(p, cF.height - 70, 4, 60);
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #ff0000; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #cc0000;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#34495e; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button>
<button class="mode-btn" id="btnWind" style="padding:10px; background:#1abc9c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🌬️ VENT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.3); padding:15px; border-radius:10px;">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.3); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise' || type === 'wind') {
const bufferSize = 2 * audioCtx.sampleRate, buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate), output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer; source.loop = true;
const filter = audioCtx.createBiquadFilter();
lfo = audioCtx.createOscillator();
const lfoGain = audioCtx.createGain();
if (type === 'noise') {
filter.type = "bandpass"; filter.frequency.value = 800; filter.Q.value = 0.7;
lfo.frequency.value = 0.6; lfoGain.gain.value = 400;
} else {
// RÉGLAGES DU VENT (Sifflement variable)
filter.type = "lowpass"; filter.frequency.value = 400; filter.Q.value = 10;
lfo.frequency.value = 0.2; lfoGain.gain.value = 300;
}
lfo.connect(lfoGain); lfoGain.connect(filter.frequency);
source.connect(filter); filter.connect(ana);
lfo.start(); source.start();
} else if (type === 'piano') {
const gInst = audioCtx.createGain();
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02;
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 3);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
gInst.connect(ana); source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (['piano', 'noise', 'wind'].includes(mode)) ? 'none' : 'block';
init(); audioCtx.resume(); isPlaying = true;
btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
updateMode('piano');
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let y = (dataWave[i] / 256) * cW.height;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'noise' || currentType === 'wind') {
for(let i=0; i<cF.width; i+=4) {
let h = (Math.random() * 40 + 5);
if (currentType === 'wind') h *= (1 - i/cF.width); // Vent plus grave (plus de basses)
ctxF.fillRect(i, cF.height - h - 10, 2, h);
}
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let posX = p * i;
if (posX < cF.width) ctxF.fillRect(posX, cF.height - (60/i) - 10, 3, 60/i);
}
} else {
ctxF.fillRect(p, cF.height - 70, 4, 60);
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const btnPlay = document.getElementById('btnPlay');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3.5 : 10;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function startLab() {
init();
if (!isPlaying) {
audioCtx.resume();
isPlaying = true;
btnPlay.textContent = "PAUSE";
btnPlay.style.background = "#e67e22";
draw();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block';
if (mode === 'diapason') currentFreq = 440;
// ACTION DIRECTE : Active le lab et joue le son
startLab();
playSound(currentFreq, currentType);
}
btnPlay.onclick = function() {
init();
if (!isPlaying) {
startLab();
playSound(currentFreq, currentType);
} else {
audioCtx.suspend();
isPlaying = false;
this.textContent = "DÉMARRER";
this.style.background = "#2ecc71";
}
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
startLab();
playSound(currentFreq, 'piano');
};
});
function draw() {
if(!isPlaying) return;
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
let rms = 0;
for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128);
let hFactor = Math.min(1, (rms / dataWave.length) * 15);
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let h = hFactor * (80/i);
ctxF.fillRect(p*i, cF.height - h - 10, 3, h);
}
} else {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1;
}
}
}
document.getElementById('btnFreqToggle').onclick = () => {
const ui = document.getElementById('controlsUI');
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
};
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
let fade = (type === 'piano') ? 3.5 : 10;
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade);
source.connect(gInst); source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block';
if (mode === 'diapason') currentFreq = 440;
if (isPlaying) playSound(currentFreq, currentType);
}
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
// On ne vide pas les canvas ici pour garder l'image "gelée"
}
};
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
document.getElementById('btnPlay').textContent = "PAUSE";
document.getElementById('btnPlay').style.background = "#e67e22";
draw();
}
playSound(currentFreq, 'piano');
};
});
function draw() {
if(!isPlaying) return; // Arrête le dessin si on est en pause
requestAnimationFrame(draw);
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
let rms = 0;
for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128);
let hFactor = Math.min(1, (rms / dataWave.length) * 15);
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) {
let h = hFactor * (80/i);
ctxF.fillRect(p*i, cF.height - h - 10, 3, h);
}
} else {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1;
}
}
}
// Setup reste des boutons
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.getElementById('btnFreqToggle').onclick = () => ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); };
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); };
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; }
.key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; }
.key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; }
.mode-btn { transition: all 0.2s; border: 2px solid transparent !important; }
.mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); }
input[type=range] { accent-color: #40ccff; cursor: pointer; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = 'sine';
source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 10);
source.connect(gInst);
source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator();
source.type = type;
source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; }
if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; }
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
}
function drawStaticSpectre() {
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10);
} else {
let p = (currentFreq / 1500) * cF.width;
ctxF.fillRect(p, 10, 4, cF.height - 20);
}
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9';
};
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase());
});
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
document.getElementById('btnPlay').textContent = "PAUSE";
document.getElementById('btnPlay').style.background = "#e67e22";
}
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔇" : "🔊";
this.style.background = isMuted ? "#e74c3c" : "#ecf0f1";
this.style.color = isMuted ? "white" : "#2c3e50";
if(gainNode) {
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
}
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
// Calcul de l'amplitude moyenne (RMS) pour l'extinction visuelle
let rms = 0;
for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128);
let hFactor = Math.min(1, (rms / dataWave.length) * 15);
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice;
// L'oscillo suit aussi l'amplitude
let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80);
}
else if (currentType === 'piano') {
// Les harmoniques du piano suivent maintenant l'extinction
for(let i=1; i<=4; i++) {
let hHeight = hFactor * (80/i);
ctxF.fillRect(p*i, cF.height - hHeight - 10, 3, hHeight);
}
}
else {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1.0;
}
}
}
}
draw();
updateMode('sine');
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; }
.key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; }
.key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; }
.mode-btn { transition: all 0.2s; border: 2px solid transparent !important; }
.mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); }
input[type=range] { accent-color: #40ccff; cursor: pointer; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(1, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP);
gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; }
if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; }
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
}
function drawStaticSpectre() {
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10);
} else {
let p = (currentFreq / 1500) * cF.width;
ctxF.fillRect(p, 10, 4, cF.height - 20);
}
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9';
};
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase());
});
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
document.getElementById('btnPlay').textContent = "PAUSE";
document.getElementById('btnPlay').style.background = "#e67e22";
}
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔇" : "🔊";
this.style.background = isMuted ? "#e74c3c" : "#ecf0f1";
this.style.color = isMuted ? "white" : "#2c3e50";
if(gainNode) {
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
}
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); }
else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
}
else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0;
}
}
}
}
draw();
updateMode('sine');
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; }
.key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; }
.key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; }
.mode-btn { transition: all 0.2s; border: 2px solid transparent !important; }
.mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); }
input[type=range] { accent-color: #40ccff; cursor: pointer; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano' || type === 'diapason') {
const gInst = audioCtx.createGain();
gInst.gain.setValueAtTime(1, audioCtx.currentTime);
if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = 'sine';
source.frequency.value = 440;
gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 10);
source.connect(gInst);
source.start();
}
gInst.connect(ana);
} else {
source = audioCtx.createOscillator();
source.type = type;
source.frequency.value = freq;
source.connect(ana); source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode'));
const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1));
if(activeBtn) activeBtn.classList.add('active-mode');
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; }
if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; }
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
}
function drawStaticSpectre() {
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10);
} else {
let p = (currentFreq / 1500) * cF.width;
ctxF.fillRect(p, 10, 4, cF.height - 20);
}
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9';
};
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase());
});
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
document.getElementById('btnPlay').textContent = "PAUSE";
document.getElementById('btnPlay').style.background = "#e67e22";
}
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
playSound(currentFreq, currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
if (isPlaying) playSound(currentFreq, currentType);
else drawStaticSpectre();
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔇" : "🔊";
this.style.background = isMuted ? "#e74c3c" : "#ecf0f1";
this.style.color = isMuted ? "white" : "#2c3e50";
if(gainNode) {
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
}
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
let rms = 0; // On calcule l'amplitude pour raccorder la hauteur du spectre
for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128);
let hFactor = Math.min(1, (rms / dataWave.length) * 15);
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80);
}
else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, cF.height - (hFactor*(80/i)) - 10, 3, hFactor*(80/i));
}
else {
ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1.0;
}
}
}
}
draw();
updateMode('sine');
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1.5; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">🔇</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;">
<div style="color:#3498db; font-size:11px; margin-bottom:8px;">Notes Fixes : <span id="pianoNote">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: none; flex-direction: column; gap: 10px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px;">Fréquence Libre : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key.active { background: #3498db; color: white; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana); source.start();
}
}
function togglePlayState() {
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
const b = document.getElementById('btnPlay');
b.textContent = "PAUSE"; b.style.background = "#e67e22";
draw();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
// Affichage piano
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
// Logique de masquage auto du curseur fréquence
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
// Si c'est un instrument, on ferme les réglages s'ils étaient ouverts (sauf volume)
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#34495e'; }
if (mode === 'diapason') currentFreq = 440;
if (isPlaying) playSound(currentFreq, currentType);
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#34495e' : '#3498db';
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
togglePlayState();
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType);
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
togglePlayState();
if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') {
playSound(currentFreq, currentType);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔊" : "🔇";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
}
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#f1c40f";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0;
}
}
}
draw();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #5d1919; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 1px solid #7d2a2a;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 2px solid #222; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 2px solid #222; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; transition: transform 0.1s;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #95a5a6; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 16px;">🔇</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ecf0f1; border:none; border-radius:6px; color:#2c3e50; font-size:11px; font-weight:bold;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#2c3e50; font-size:11px; font-weight:bold;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#7f8c8d; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.3); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.1);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.4); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.1);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%; cursor: pointer;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; cursor: pointer;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; }
.key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; }
.key.active { background: #3498db; color: white; box-shadow: 0 4px #2980b9; }
input[type=range] { accent-color: #3498db; }
.mode-btn:hover { opacity: 0.9; transform: scale(1.02); }
.mode-btn:active { transform: scale(0.98); }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana); source.start();
}
}
function togglePlayState() {
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
const b = document.getElementById('btnPlay');
b.textContent = "PAUSE"; b.style.background = "#e67e22";
draw();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.border = 'none');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.border = '2px solid white';
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; }
if (mode === 'diapason') currentFreq = 440;
if (isPlaying) playSound(currentFreq, currentType);
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9';
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
togglePlayState();
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType);
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
togglePlayState();
if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') {
playSound(currentFreq, currentType);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔊" : "🔇";
this.style.background = isMuted ? "#e74c3c" : "#95a5a6";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
}
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0;
}
}
}
draw();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #a51212; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #7a0d0d;">
<h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button>
<button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔇</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
<button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);">
<div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Note : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);">
<div id="freqContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;">
</div>
<div id="volContainer">
<label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; }
.key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; }
.key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; }
.mode-btn:active { transform: translateY(2px); box-shadow: none !important; }
#btnPlay:active { transform: translateY(2px); box-shadow: none !important; }
input[type=range] { accent-color: #40ccff; cursor: pointer; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const ui = document.getElementById('controlsUI');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana); source.start();
}
}
function togglePlayState() {
if (!isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
const b = document.getElementById('btnPlay');
b.textContent = "PAUSE"; b.style.background = "#e67e22"; b.style.boxShadow = "0 4px 0 #d35400";
draw();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.border = 'none');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.border = '2px solid white';
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise');
document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block';
if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; }
if (mode === 'diapason') currentFreq = 440;
if (isPlaying) playSound(currentFreq, currentType);
}
document.getElementById('btnFreqToggle').onclick = function() {
ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none';
this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9';
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
togglePlayState();
playSound(currentFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; this.style.boxShadow = "0 4px 0 #d35400";
updateMode(currentType);
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; this.style.boxShadow = "0 4px 0 #219150";
}
};
fRange.oninput = () => {
currentFreq = parseFloat(fRange.value);
fLabel.textContent = Math.round(currentFreq);
togglePlayState();
if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') {
playSound(currentFreq, currentType);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔊" : "🔇";
this.style.background = isMuted ? "#e74c3c" : "#ecf0f1";
this.style.color = isMuted ? "white" : "#2c3e50";
this.style.boxShadow = isMuted ? "0 4px 0 #c0392b" : "0 4px 0 #95a5a6";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
}
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#ffeb3b";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let p = (currentFreq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0;
}
}
}
draw();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;">
<div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div>
<label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key.active { background: #3498db; color: white; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440;
let offset = 0;
let lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana);
source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator();
sPiano2 = audioCtx.createOscillator();
sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq;
sPiano2.frequency.value = freq * 2.01;
sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana);
source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block';
if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode);
}
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentPianoFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
// Si on clique sur une touche piano alors qu'on est en "Pause", on démarre automatiquement le son
if(!isPlaying) {
isPlaying = true;
const b = document.getElementById('btnPlay');
b.textContent = "PAUSE"; b.style.background = "#e67e22";
audioCtx.resume();
}
playSound(currentPianoFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true;
this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false;
this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
fRange.oninput = () => {
fLabel.textContent = fRange.value;
if (isPlaying && source && !sPiano2 && currentType !== 'diapason') {
source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
}
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice;
let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#f1c40f";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value);
let p = (freq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5;
ctxF.fillRect(p*3, 40, 2, cF.height - 50);
ctxF.fillRect(p*5, 60, 2, cF.height - 70);
ctxF.globalAlpha = 1.0;
}
}
}
draw();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1.5; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button>
<button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">⚙️ FRÉQUENCE</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">🔇</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;">
<div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;">
<div id="freqContainer" style="display: block;">
<label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div>
<label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key.active { background: #3498db; color: white; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440;
let offset = 0, lastNoiseHeights = [];
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana); source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator();
source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine';
source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana); source.start();
}
}
function togglePlayState(forcePlay = true) {
if (forcePlay && !isPlaying) {
init(); audioCtx.resume(); isPlaying = true;
const b = document.getElementById('btnPlay');
b.textContent = "PAUSE"; b.style.background = "#e67e22";
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
// On cache aussi la fréquence auto pour piano/diapason/noise
document.getElementById('freqContainer').style.opacity = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? '0.3' : '1';
document.getElementById('freqContainer').style.pointerEvents = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'auto';
if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode);
}
document.getElementById('btnFreqToggle').onclick = function() {
const fc = document.getElementById('freqContainer');
fc.style.display = (fc.style.display === 'none') ? 'block' : 'none';
this.style.background = (fc.style.display === 'none') ? '#2c3e50' : '#34495e';
};
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentPianoFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
togglePlayState();
playSound(currentPianoFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
fRange.oninput = () => {
fLabel.textContent = fRange.value;
togglePlayState();
if (source && !sPiano2 && currentType !== 'diapason' && currentType !== 'noise') {
source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05);
} else {
updateMode(currentType);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "🔊" : "🔇";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2.5) % 200;
}
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#f1c40f";
if (currentType === 'noise') {
for(let i=0; i<cF.width; i+=4) {
if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50;
ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]);
}
} else {
let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value);
let p = (freq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0;
}
}
}
draw();
</script>
</div>
<div style="width: 100%; max-width: 500px; background: #2c3e50; border-radius: 15px; padding: 20px; color: white; font-family: sans-serif; text-align: center; box-shadow: 0 4px 15px rgba(0,0,0,0.3);">
<h3 style="margin-top: 0; font-size: 18px;">Visualiseur de Son</h3>
<canvas id="oscillo" style="width: 100%; height: 120px; background: #1a1a1a; border-radius: 8px; border: 1px solid #444;"></canvas>
<div style="margin: 20px 0;">
<button id="btnPlay" style="background: #e74c3c; border: none; padding: 10px 25px; color: white; border-radius: 50px; cursor: pointer; font-weight: bold; transition: 0.3s;">
DÉMARRER SON
</button>
</div>
<div style="font-size: 14px; opacity: 0.8;">
Fréquence : <span id="fLabel">440</span> Hz
<input type="range" id="fRange" min="200" max="800" value="440" style="width: 100%; margin-top: 10px; accent-color: #3498db;">
</div>
<script>
let audioCtx, osc, ana, data;
let playing = false;
const btn = document.getElementById('btnPlay');
const canvas = document.getElementById('oscillo');
const ctx = canvas.getContext('2d');
const range = document.getElementById('fRange');
const label = document.getElementById('fLabel');
function setup() {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
osc = audioCtx.createOscillator();
ana = audioCtx.createAnalyser();
ana.fftSize = 2048;
osc.connect(ana);
ana.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
osc.start();
}
btn.onclick = () => {
if (!audioCtx) setup();
if (!playing) {
audioCtx.resume();
btn.textContent = "STOP";
btn.style.background = "#27ae60";
playing = true;
draw();
} else {
audioCtx.suspend();
btn.textContent = "DÉMARRER SON";
btn.style.background = "#e74c3c";
playing = false;
}
};
function draw() {
if (!playing) return;
requestAnimationFrame(draw);
osc.frequency.value = range.value;
label.textContent = range.value;
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#1a1a1a";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 2;
ctx.strokeStyle = "#3498db";
ctx.beginPath();
let sliceWidth = canvas.width / data.length;
let x = 0;
for (let i = 0; i < data.length; i++) {
let v = data[i] / 128.0;
let y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.stroke();
}
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 15px 0; color: #3498db;">Laboratoire d'Ondes Sonores</h3>
<canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: flex; gap: 5px; margin-bottom: 15px;">
<button id="btnSine" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-weight:bold;">SINUS</button>
<button id="btnSquare" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">CARRÉ</button>
<button id="btnTriangle" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">TRIANGLE</button>
</div>
<div style="background: #2c3e50; padding: 15px; border-radius: 8px;">
<label style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;">
</div>
<script>
let audioCtx, osc, ana, gainNode, data;
let isPlaying = false;
let isMuted = false;
const btnPlay = document.getElementById('btnPlay');
const btnMute = document.getElementById('btnMute');
const btnSine = document.getElementById('btnSine');
const btnSquare = document.getElementById('btnSquare');
const btnTriangle = document.getElementById('btnTriangle');
const canvas = document.getElementById('oscillo');
const ctx = canvas.getContext('2d');
const range = document.getElementById('fRange');
const label = document.getElementById('fLabel');
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
osc = audioCtx.createOscillator();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
osc.type = 'sine';
osc.frequency.setValueAtTime(range.value, audioCtx.currentTime);
osc.connect(ana);
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
osc.start();
}
}
// Gestionnaire de changement d'onde
function updateWave(type) {
if (!audioCtx) initAudio();
if (osc) osc.type = type;
// Mise à jour visuelle des boutons
[btnSine, btnSquare, btnTriangle].forEach(btn => btn.style.background = '#444');
if(type === 'sine') btnSine.style.background = '#3498db';
if(type === 'square') btnSquare.style.background = '#3498db';
if(type === 'triangle') btnTriangle.style.background = '#3498db';
}
// Ecouteurs d'événements (plus fiables que onclick dans le HTML)
btnSine.addEventListener('click', () => updateWave('sine'));
btnSquare.addEventListener('click', () => updateWave('square'));
btnTriangle.addEventListener('click', () => updateWave('triangle'));
btnPlay.onclick = () => {
initAudio();
if (!isPlaying) {
audioCtx.resume();
btnPlay.textContent = "PAUSE";
btnPlay.style.background = "#e67e22";
isPlaying = true;
draw();
} else {
audioCtx.suspend();
btnPlay.textContent = "DÉMARRER";
btnPlay.style.background = "#27ae60";
isPlaying = false;
}
};
btnMute.onclick = () => {
if (!audioCtx) initAudio();
isMuted = !isMuted;
gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.02);
btnMute.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
btnMute.style.background = isMuted ? "#c0392b" : "#7f8c8d";
};
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
osc.frequency.value = range.value;
label.textContent = range.value;
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3;
ctx.strokeStyle = "#3498db";
ctx.beginPath();
let sliceWidth = canvas.width / data.length;
let x = 0;
for (let i = 0; i < data.length; i++) {
let v = data[i] / 128.0;
let y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.stroke();
}
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 15px 0; color: #3498db;">Laboratoire d'Ondes Sonores</h3>
<canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: flex; gap: 5px; margin-bottom: 15px;">
<button id="btnSine" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-weight:bold;">SINUS</button>
<button id="btnSquare" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">CARRÉ</button>
<button id="btnTriangle" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">TRIANGLE</button>
</div>
<div style="background: #2c3e50; padding: 15px; border-radius: 8px;">
<label style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;">
</div>
<script>
let audioCtx, osc, ana, gainNode, data;
let isPlaying = false;
let isMuted = false;
let currentType = 'sine';
const btnPlay = document.getElementById('btnPlay');
const btnMute = document.getElementById('btnMute');
const btnSine = document.getElementById('btnSine');
const btnSquare = document.getElementById('btnSquare');
const btnTriangle = document.getElementById('btnTriangle');
const canvas = document.getElementById('oscillo');
const ctx = canvas.getContext('2d');
const range = document.getElementById('fRange');
const label = document.getElementById('fLabel');
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
osc = audioCtx.createOscillator();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
osc.type = currentType;
osc.frequency.setValueAtTime(range.value, audioCtx.currentTime);
osc.connect(ana);
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
osc.start();
}
}
function updateWave(type) {
currentType = type;
if (osc) osc.type = type;
[btnSine, btnSquare, btnTriangle].forEach(btn => btn.style.background = '#444');
document.getElementById('btn' + type.charAt(0).toUpperCase() + type.slice(1)).style.background = '#3498db';
// Si on est en pause, on force un dessin statique pour montrer le changement
if (!isPlaying) {
drawStatic(type);
}
}
// Fonction pour dessiner l'onde même quand le son est coupé
function drawStatic(type) {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3;
ctx.strokeStyle = "#3498db";
ctx.beginPath();
let freqFactor = range.value / 100;
for (let x = 0; x < canvas.width; x++) {
let angle = (x / canvas.width) * Math.PI * 2 * freqFactor;
let y;
if (type === 'sine') y = Math.sin(angle);
else if (type === 'square') y = Math.sin(angle) >= 0 ? 1 : -1;
else if (type === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(angle));
let posY = (y * canvas.height / 3) + (canvas.height / 2);
if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY);
}
ctx.stroke();
}
btnSine.onclick = () => updateWave('sine');
btnSquare.onclick = () => updateWave('square');
btnTriangle.onclick = () => updateWave('triangle');
btnPlay.onclick = () => {
initAudio();
if (!isPlaying) {
audioCtx.resume();
btnPlay.textContent = "PAUSE";
btnPlay.style.background = "#e67e22";
isPlaying = true;
draw();
} else {
audioCtx.suspend();
btnPlay.textContent = "DÉMARRER";
btnPlay.style.background = "#27ae60";
isPlaying = false;
}
};
btnMute.onclick = () => {
if (!audioCtx) initAudio();
isMuted = !isMuted;
gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.02);
btnMute.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
btnMute.style.background = isMuted ? "#c0392b" : "#7f8c8d";
};
range.oninput = () => {
label.textContent = range.value;
if (osc) osc.frequency.value = range.value;
if (!isPlaying) drawStatic(currentType);
};
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3;
ctx.strokeStyle = "#3498db";
ctx.beginPath();
let sliceWidth = canvas.width / data.length;
let x = 0;
for (let i = 0; i < data.length; i++) {
let v = data[i] / 128.0;
let y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.stroke();
}
// Premier dessin à vide
drawStatic('sine');
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 15px 0; color: #3498db;">Analyseur de Sons Complexes</h3>
<canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 10px; margin: 15px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px; margin-bottom: 10px;">
<button id="btnSine" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-size:11px;">SINUS</button>
<button id="btnSquare" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px;">CARRÉ</button>
<button id="btnTriangle" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px;">TRIANGLE</button>
<button id="btnPiano" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px; font-weight:bold;">🎹 PIANO</button>
<button id="btnNoise" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px; font-weight:bold;">💨 BRUIT</button>
</div>
<div style="background: #2c3e50; padding: 15px; border-radius: 8px;">
<label id="labelType" style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;">
</div>
<script>
let audioCtx, osc, ana, gainNode, data, noiseNode;
let isPlaying = false, isMuted = false, currentType = 'sine';
const notes = { 261:"DO", 293:"RÉ", 329:"MI", 349:"FA", 392:"SOL", 440:"LA", 493:"SI" };
const buttons = {
sine: document.getElementById('btnSine'),
square: document.getElementById('btnSquare'),
triangle: document.getElementById('btnTriangle'),
piano: document.getElementById('btnPiano'),
noise: document.getElementById('btnNoise')
};
const canvas = document.getElementById('oscillo');
const ctx = canvas.getContext('2d');
const range = document.getElementById('fRange');
const label = document.getElementById('fLabel');
const labelType = document.getElementById('labelType');
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
createSource();
}
}
function createSource() {
if (osc) { osc.stop(); osc.disconnect(); }
if (noiseNode) { noiseNode.stop(); noiseNode.disconnect(); }
if (currentType === 'noise') {
const bufferSize = 2 * audioCtx.sampleRate,
buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate),
output = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1;
noiseNode = audioCtx.createBufferSource();
noiseNode.buffer = buffer;
noiseNode.loop = true;
noiseNode.connect(ana);
noiseNode.start();
} else if (currentType === 'piano') {
// Synthèse additive simple pour le timbre piano
osc = audioCtx.createOscillator();
osc.type = 'triangle';
const osc2 = audioCtx.createOscillator(); // Harmonique 2
osc2.type = 'sine';
const g2 = audioCtx.createGain(); g2.gain.value = 0.5;
osc.connect(ana);
osc2.connect(g2); g2.connect(ana);
osc.frequency.value = range.value;
osc2.frequency.value = range.value * 2;
osc.start(); osc2.start();
// On stocke le 2eme osc dans une propriété pour le couper plus tard
osc.slave = osc2;
} else {
osc = audioCtx.createOscillator();
osc.type = currentType;
osc.frequency.value = range.value;
osc.connect(ana);
osc.start();
}
ana.connect(gainNode);
}
function updateMode(type) {
currentType = type;
Object.values(buttons).forEach(b => b.style.background = '#444');
buttons[type].style.background = '#3498db';
if (type === 'piano') {
range.min = 261; range.max = 493; range.step = 1;
labelType.innerHTML = "Note : <span id='fLabel' style='color:#3498db;font-weight:bold;'>LA</span>";
} else if (type === 'noise') {
labelType.innerHTML = "Intensité du bruit";
} else {
range.min = 100; range.max = 1000; range.step = 1;
labelType.innerHTML = "Fréquence : <span id='fLabel' style='color:#3498db;font-weight:bold;'>440</span> Hz";
}
if (audioCtx) createSource();
if (!isPlaying) drawStatic();
}
Object.keys(buttons).forEach(key => {
buttons[key].onclick = () => updateMode(key);
});
document.getElementById('btnPlay').onclick = function() {
initAudio();
if (!isPlaying) {
audioCtx.resume();
this.textContent = "PAUSE"; this.style.background = "#e67e22";
isPlaying = true; draw();
} else {
audioCtx.suspend();
this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
isPlaying = false;
}
};
range.oninput = () => {
if (currentType === 'piano') {
let closest = Object.keys(notes).reduce((a, b) => Math.abs(b - range.value) < Math.abs(a - range.value) ? b : a);
document.getElementById('fLabel').textContent = notes[closest];
if (osc) {
osc.frequency.setTargetAtTime(closest, audioCtx.currentTime, 0.05);
if(osc.slave) osc.slave.frequency.setTargetAtTime(closest*2, audioCtx.currentTime, 0.05);
}
} else {
document.getElementById('fLabel').textContent = range.value;
if (osc) osc.frequency.value = range.value;
}
if (!isPlaying) drawStatic();
};
function drawStatic() {
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath();
for (let x = 0; x < canvas.width; x++) {
let y = 0;
let angle = (x / canvas.width) * Math.PI * 2 * (range.value / 100);
if (currentType === 'sine') y = Math.sin(angle);
else if (currentType === 'square') y = Math.sin(angle) >= 0 ? 1 : -1;
else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(angle));
else if (currentType === 'piano') y = (Math.sin(angle) + 0.5 * Math.sin(angle * 2));
else if (currentType === 'noise') y = Math.random() * 2 - 1;
let posY = (y * canvas.height / 4) + (canvas.height / 2);
if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY);
}
ctx.stroke();
}
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let sliceWidth = canvas.width / data.length;
let x = 0;
for (let i = 0; i < data.length; i++) {
let v = data[i] / 128.0;
let y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.stroke();
}
drawStatic();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire Acoustique Avancé</h3>
<canvas id="oscillo" width="600" height="180" style="width: 100%; height: 140px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:10px; border-radius:8px;">
<div style="display:flex; justify-content:center; gap:2px;">
<button class="key" data-f="261.63">DO</button>
<button class="key" data-f="293.66">RÉ</button>
<button class="key" data-f="329.63">MI</button>
<button class="key" data-f="349.23">FA</button>
<button class="key" data-f="392.00">SOL</button>
<button class="key" data-f="440.00">LA</button>
<button class="key" data-f="493.88">SI</button>
</div>
</div>
<div id="freqControl" style="background: #2c3e50; padding: 10px; border-radius: 8px;">
<label style="display: block; font-size: 13px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1200" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<style>
.key { background: white; color: black; border: none; padding: 10px 5px; width: 35px; border-radius: 0 0 3px 3px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key:active { background: #ddd; }
.key.active { background: #3498db; color: white; }
</style>
<script>
let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine';
const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel');
const pianoI = document.getElementById('pianoInterface'), freqC = document.getElementById('freqControl');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
}
}
function stopSource() {
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
}
function playSound(freq, type) {
init();
stopSource();
if (isMuted) gainNode.gain.value = 0; else gainNode.gain.value = 1;
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) output[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer; source.loop = true;
} else if (type === 'piano') {
source = audioCtx.createOscillator();
source.type = 'square'; // Base plus riche
const g = audioCtx.createGain();
// Enveloppe Piano : Frappe puis extinction
g.gain.setValueAtTime(0.5, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.5);
source.connect(g); g.connect(ana);
source.frequency.value = freq;
source.start();
return;
} else {
source = audioCtx.createOscillator();
source.type = type === 'diapason' ? 'sine' : type;
source.frequency.value = type === 'diapason' ? 440 : freq;
}
source.connect(ana);
source.start();
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
pianoI.style.display = (mode === 'piano') ? 'block' : 'none';
freqC.style.display = (mode === 'diapason' || mode === 'piano') ? 'none' : 'block';
if (mode === 'diapason') fLabel.textContent = "440";
if (isPlaying) playSound(fRange.value, mode);
else drawStatic();
}
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase());
});
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
if(isPlaying) playSound(key.dataset.f, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true;
this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false;
this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
if (gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.05);
this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
};
fRange.oninput = () => { fLabel.textContent = fRange.value; if(source && isPlaying) source.frequency.value = fRange.value; if(!isPlaying) drawStatic(); };
function drawStatic() {
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let freq = currentType === 'diapason' ? 440 : fRange.value;
for (let x = 0; x < canvas.width; x++) {
let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100);
if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a);
else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7;
else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a));
else if (currentType === 'piano') y = (Math.sin(a) + 0.3*Math.sin(a*2) + 0.2*Math.sin(a*3));
else if (currentType === 'noise') y = Math.random() * 2 - 1;
let posY = (y * canvas.height / 4) + (canvas.height / 2);
if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY);
}
ctx.stroke();
}
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let slice = canvas.width / data.length, x = 0;
for (let i = 0; i < data.length; i++) {
let y = (data[i] / 128.0) * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += slice;
}
ctx.stroke();
}
drawStatic();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire Acoustique : Fréquence & Amplitude</h3>
<canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:10px; border-radius:8px;">
<div style="display:flex; justify-content:center; gap:2px;">
<button class="key" data-f="261.63">DO</button>
<button class="key" data-f="293.66">RÉ</button>
<button class="key" data-f="329.63">MI</button>
<button class="key" data-f="349.23">FA</button>
<button class="key" data-f="392.00">SOL</button>
<button class="key" data-f="440.00">LA</button>
<button class="key" data-f="493.88">SI</button>
</div>
</div>
<div style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;">
<div id="freqContainer">
<label style="display: block; font-size: 13px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1200" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div>
<label style="display: block; font-size: 13px;">Volume (Amplitude) : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 10px 5px; width: 35px; border-radius: 0 0 3px 3px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key.active { background: #3498db; color: white; }
input[type=range] { cursor: pointer; }
</style>
<script>
let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine';
const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel');
const vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const pianoI = document.getElementById('pianoInterface'), freqContainer = document.getElementById('freqContainer');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode);
gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
let vol = isMuted ? 0 : vRange.value / 100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2;
const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate);
const output = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) output[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource();
source.buffer = buffer; source.loop = true;
} else if (type === 'piano') {
source = audioCtx.createOscillator();
source.type = 'square';
const pianoGain = audioCtx.createGain();
pianoGain.gain.setValueAtTime(0.4, audioCtx.currentTime);
pianoGain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.5);
source.connect(pianoGain); pianoGain.connect(ana);
source.frequency.value = freq;
source.start(); return;
} else {
source = audioCtx.createOscillator();
source.type = type === 'diapason' ? 'sine' : type;
source.frequency.value = type === 'diapason' ? 440 : freq;
}
source.connect(ana);
source.start();
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
pianoI.style.display = (mode === 'piano') ? 'block' : 'none';
freqContainer.style.visibility = (mode === 'diapason' || mode === 'noise') ? 'hidden' : 'visible';
if (isPlaying) playSound(fRange.value, mode);
else drawStatic();
}
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase());
});
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
if(isPlaying) playSound(key.dataset.f, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true;
this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false;
this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value / 100, audioCtx.currentTime, 0.02);
if(!isPlaying) drawStatic();
};
fRange.oninput = () => {
fLabel.textContent = fRange.value;
if(source && isPlaying && currentType !== 'noise') source.frequency.value = fRange.value;
if(!isPlaying) drawStatic();
};
function drawStatic() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let freq = currentType === 'diapason' ? 440 : fRange.value;
let amp = vRange.value / 100;
for (let x = 0; x < canvas.width; x++) {
let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100);
if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a);
else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7;
else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a));
else if (currentType === 'piano') y = (Math.sin(a) + 0.3*Math.sin(a*2));
else if (currentType === 'noise') y = Math.random() * 2 - 1;
let posY = (y * amp * canvas.height / 2.5) + (canvas.height / 2);
if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY);
}
ctx.stroke();
}
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let slice = canvas.width / data.length, x = 0;
for (let i = 0; i < data.length; i++) {
let y = (data[i] / 128.0) * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += slice;
}
ctx.stroke();
}
drawStatic();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire : Analyse des Sons</h3>
<canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON (440Hz)</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;">
<div style="color:#3498db; font-size:12px; margin-bottom:10px;">Fondamentale : <span id="pianoNote">---</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;">
<div id="freqContainer">
<label style="display: block; font-size: 13px;">Fréquence Fondamentale : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div>
<label style="display: block; font-size: 13px;">Amplitude (Volume) : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 15px 5px; width: 40px; border-radius: 0 0 4px 4px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
.key:active, .key.active { background: #3498db; color: white; transform: translateY(2px); }
</style>
<script>
let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440;
const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
const pianoI = document.getElementById('pianoInterface'), controlsUI = document.getElementById('controlsUI'), freqContainer = document.getElementById('freqContainer');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser();
gainNode = audioCtx.createGain();
ana.fftSize = 2048;
ana.connect(gainNode); gainNode.connect(audioCtx.destination);
data = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
} else if (type === 'piano') {
source = audioCtx.createOscillator(); source.type = 'triangle';
const s2 = audioCtx.createOscillator(); s2.type = 'sine'; s2.frequency.value = freq * 2;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(0.5, audioCtx.currentTime);
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.2);
source.connect(gP); s2.connect(gP); gP.connect(ana);
source.frequency.value = freq; source.start(); s2.start();
source.onended = () => { s2.stop(); s2.disconnect(); };
return;
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
}
source.connect(ana); source.start();
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
pianoI.style.display = (mode === 'piano') ? 'block' : 'none';
freqContainer.style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block';
if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode);
drawStatic();
}
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentPianoFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if(isPlaying) playSound(currentPianoFreq, 'piano');
else drawStatic();
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType); draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); drawStatic(); };
fRange.oninput = () => { fLabel.textContent = fRange.value; drawStatic(); };
function drawStatic() {
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value);
let amp = vRange.value / 100;
for (let x = 0; x < canvas.width; x++) {
let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100);
if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a);
else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7;
else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a));
else if (currentType === 'piano') y = (Math.sin(a) + 0.4 * Math.sin(a * 2)); // Fondamentale + Harmonique
else if (currentType === 'noise') y = Math.random() * 2 - 1;
let posY = (y * amp * canvas.height / 2.5) + (canvas.height / 2);
if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY);
}
ctx.stroke();
}
function draw() {
if (!isPlaying) return;
requestAnimationFrame(draw);
ana.getByteTimeDomainData(data);
ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath();
let slice = canvas.width / data.length, x = 0;
for (let i = 0; i < data.length; i++) {
let y = (data[i] / 128.0) * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
x += slice;
}
ctx.stroke();
}
drawStatic();
</script>
</div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);">
<h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3>
<div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div>
<canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div>
<canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas>
<div style="display: flex; gap: 8px; margin: 10px 0;">
<button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button>
<button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">COUPER SON</button>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;">
<button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button>
<button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button>
<button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button>
<button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button>
<button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button>
<button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button>
</div>
<div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;">
<div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div>
<div style="display:flex; justify-content:center; gap:4px;">
<button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button>
<button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button>
<button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button>
<button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button>
<button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button>
<button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button>
<button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button>
</div>
</div>
<div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;">
<div id="freqContainer">
<label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label>
<input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;">
</div>
<div>
<label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label>
<input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;">
</div>
</div>
<style>
.key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; }
.key.active { background: #3498db; color: white; }
</style>
<script>
let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440;
let offset = 0;
const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d');
const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d');
const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel');
function init() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain();
ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination);
dataWave = new Uint8Array(ana.frequencyBinCount);
}
}
function playSound(freq, type) {
init();
if (source) { try { source.stop(); } catch(e){} source.disconnect(); }
if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); }
if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); }
let vol = isMuted ? 0 : vRange.value/100;
gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02);
if (type === 'noise') {
const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0);
for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1;
source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true;
source.connect(ana);
source.start();
} else if (type === 'piano') {
source = audioCtx.createOscillator();
sPiano2 = audioCtx.createOscillator();
sPiano3 = audioCtx.createOscillator();
source.type = 'sine';
sPiano2.type = 'triangle';
sPiano3.type = 'sine';
source.frequency.value = freq;
sPiano2.frequency.value = freq * 2.01; // Légèrement désaccordé pour le réalisme
sPiano3.frequency.value = freq * 3;
const gP = audioCtx.createGain();
gP.gain.setValueAtTime(vol, audioCtx.currentTime);
// Temps du piano allongé à 3.5 secondes
gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5);
source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana);
source.start(); sPiano2.start(); sPiano3.start();
} else {
source = audioCtx.createOscillator();
source.type = (type === 'diapason') ? 'sine' : type;
source.frequency.value = (type === 'diapason') ? 440 : freq;
source.connect(ana);
source.start();
}
}
function updateMode(mode) {
currentType = mode;
document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444');
document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db';
document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none';
document.getElementById('freqContainer').style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block';
if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode);
}
document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()));
document.querySelectorAll('.key').forEach(key => {
key.onclick = () => {
document.querySelectorAll('.key').forEach(k => k.classList.remove('active'));
key.classList.add('active');
currentPianoFreq = parseFloat(key.dataset.f);
document.getElementById('pianoNote').textContent = key.dataset.n;
if(isPlaying) playSound(currentPianoFreq, 'piano');
};
});
document.getElementById('btnPlay').onclick = function() {
init();
if (!isPlaying) {
audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22";
updateMode(currentType);
draw();
} else {
audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60";
}
};
fRange.oninput = () => {
fLabel.textContent = fRange.value;
if (isPlaying && source && !sPiano2 && currentType !== 'diapason') {
source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05);
}
};
vRange.oninput = () => {
vLabel.textContent = vRange.value;
if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02);
};
document.getElementById('btnMute').onclick = function() {
isMuted = !isMuted;
this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON";
this.style.background = isMuted ? "#c0392b" : "#7f8c8d";
if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05);
};
function draw() {
requestAnimationFrame(draw);
if(isPlaying) {
ana.getByteTimeDomainData(dataWave);
offset = (offset + 2) % 200; // Vitesse de défilement vers la droite
}
// Oscilloscope
ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height);
ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2;
let slice = cW.width / dataWave.length;
for (let i = 0; i < dataWave.length; i++) {
// L'offset crée le mouvement vers la droite
let index = (i + Math.floor(offset)) % dataWave.length;
let x = i * slice;
let y = (dataWave[index] / 128.0) * cW.height / 2;
if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y);
}
ctxW.stroke();
// Spectre
ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height);
ctxF.fillStyle = "#f1c40f";
let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value);
let p = (freq / 1500) * cF.width;
if (currentType === 'sine' || currentType === 'diapason') {
ctxF.fillRect(p, 10, 4, cF.height - 20);
} else if (currentType === 'noise') {
ctxF.fillRect(0, 70, cF.width, 10);
} else if (currentType === 'piano') {
for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10));
} else {
ctxF.fillRect(p, 10, 3, cF.height - 20);
ctxF.globalAlpha = 0.5;
ctxF.fillRect(p*3, 40, 2, cF.height - 50);
ctxF.fillRect(p*5, 60, 2, cF.height - 70);
ctxF.globalAlpha = 1.0;
}
}
draw();
</script>
</div>
ACTIVITÉ SON CHAP 8 BTS
COLLEGE SOUTINE
Created on February 18, 2026
Start designing with a free template
Discover more than 1500 professional designs like these:
View
Wizardry Letter
View
Search Bar Card
View
Piñata
View
Microlearning: When to Use Chat, Meetings or Email
View
Magazine dossier
View
Microlearning: Graphic Design
View
Microlearning: Enhance Your Wellness and Reduce Stress
Explore all templates
Transcript
BTS FED
ACTIVITÉ SON
Start
A game created by TON PROF PRÉFÉRÉ
BTS FED
pRÊTS à DÉCOUVRIR LA NATURE DU SON ?
Missions
A game created by TON PROF PRÉFÉRÉ
I am a cool subtitle, perfect for providing more context about the topic you are going to address
ACTIVITÉ SON
Quel est ton prénom ?
suivant
§classe§: *2
C'est parti !
Quelle est ta classe ?
§nom§: *3
* ATTENTION : ce prénom sera écrit sur ton diplôme à la fin
SON
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
SPÉCIALISTE SON
I LE SON
?$S1|=|1
II INTENSITÉ SONORE
?$S2|=|1
?$S1|=|1&S2|=|1
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
EXERCICES
I am a cool subtitle, perfect for providing more context about the topic you are going to address
SPÉCIALISTE SON
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
§nom§
ANNEE
I LE SON
16,96ms
10 périodes
f =
T =
Hz
x 10 s
ms =
VALIDER
SON PUR
ANALYSER CES SONS ET RANGER LES DANS LA BONNE CATÉGORIE
$S1|=|1
SON COMPLEXE
BRUIT
II INTENSITÉ SONORE
Le bruit devient dangereux pour l'audition à partir de 85 décibels (dB), surtout en cas d'exposition prolongée. Ce seuil est reconnu par l'Organisation mondiale de la santé (OMS) et les autorités sanitaires comme le niveau à ne pas dépasser sur une journée de travail de 8 heures.
Calculer le niveau sonore L d'une perceuse dont l’intensité est de 3,5.10-4W.m-2
$S2|=|1
L= dB
Ce son est-il potentiellement dangereux ?
VALIDER
missions
exercices
?$son1|=|1&son2|=|1&son3|=|1&son4|=|1
son pur vs son complexe
définition
étude d'un son
étude d'un source sonore
?$son4|=|1
?$son2|=|1
?$son3|=|1
?$son1|=|1
Déplacer les mots pour compléter les phrases ci-dessous :
PÉRIODE
VIBRATION
PUR
NIVEAU
BRUIT
INTENSITÉ
FRÉQUENCE
MÉCANIQUE
120 dB
COMPLEXE
90 dB
Le son est une onde ______ longitudinale qui se propage sous forme de __________ . La ____ d’un son correspond au nombre de vibrations par seconde, elle s’exprime en Hertz. La ______ correspond au temps d’une vibration complète ; elle s’exprime en seconde. L’ _________ sonore s’exprime en W.m² et mesure la puissance transportée par le son par unité de surface. Pour comparer les sons, on utilise le _________ sonore, exprimé en décibel. Un son devient dangereux pour l’audition à partir de ______ dB pour une exposition prolongée, et il peut être immédiatement dangereux au‑delà de ______ dB. Un son ____ est un son composé d’une seule fréquence. Un son __________ est formé de plusieurs fréquences superposées. Le _______ est un son dont les variations sont irrégulières et désagréables.
$son1|=|1
Complétez le tableau
t (s)
t (s)
$son2|=|1
t (s)
BRUIT
SON PUR
SON COMPLEXE
Complétez le tableau
t (s)
$son2|=|1
t (s)
t (s)
BRUIT
SON COMPLEXE
SON PUR
L’enregistrement d’une onde sonore est représenté sur ce graphique.Quelles sont les caractéristiques de ce son ?
Étude d'un son
$son3|=|1
T =
f =
Hz
nature * =
VALIDER
nature * = simple/composé/ bruit
Étude d'une source sonore
Une source sonore a une intensité I1 = 7,2.10-3 W.m-2 à une distance R1 = 10 m de cette source.1. Calculer le niveau sonore L12. Déterminer la puissance P de la source, sachant qu’elle émet dans toutes les directions de l’espace. 3. Dire si cette source présente un danger
$son4|=|1
L =
dB
P =
Cette source est-elle dangereuse ? =
VALIDER
Mission 1
Left or right?
Let's start with something simple: answer the questions by choosing the correct image. Good luck!
Start
mission 1
Hint: A great title
1/3
How much information does our brain retain through visual stimuli such as images, interactivities,or animations?
mission 1
Hint: A great title
2/3
What would you use in your presentation to entertain, provide relevant information, and capture the attention ofyour audience?
mission 1
Hint: A great title
3/3
What percentage of our brain is involved in processing visual stimuli, such as images, interactivities, or animation?
Mission 1 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 2
Mission 3
Mission 4
Mission 5
COMPLETE!
Mission 2
Which one doesn't fit?
Now come three demanding tests! You will see in each of them an instrument that DOES NOT FIT for some reason. Which one?
Start
mission 2
1/3
Hint: A great title
mission 2
2/3
Hint: A great title
mission 2
3/3
Hint: A great title
Mission 2 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Recover all band members!
Mission 3
Mission 4
Mission 5
COMPLETE!
COMPLETE!
mission 3
IDENTIFY THE letter
Do you have a good memory? Choose the missing word correctly to complete the lyrics of these famous compositions.
Start
mission 3
1/3
Hint: A great title
Did you know that ________ allows you to share your creation directly, without the need for downloads?
Enter an incorrect
Enter the correct answer ✔
Enter an incorrect
mission 3
2/3
Hint: A great title
Did you know that images illustrate what you want to convey and are a _____ to add additional info?
Write here an incorrect answer
Write here an incorrect answer
Write here the correct answer ✔
mission 3
3/3
Hint: A cool title
Did you know that ________ are an aesthetic resource that tell stories by themselves and also keep the brain awake?
Write here an incorrect answer
Write here the correct answer ✔
Write here an answer incorrect
Mission 3 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 4
Mission 5
Complete!
Complete!
Complete!
mission 4
What musical notes are these?
Next you will see three staves with several notes in treble clef. Can you recognize them?
Start
mission 4
1/3
Hint: A great title
Re Sol La Mi
Mi Sol Re Do
Fa La Si Sol ✔
mission 4
2/3
Hint: A great title
Do Re Si Mi
Re Mi Do Fa ✔
Re Sol Mi Fa
mission 4
3/3
Hint: A great title
Do Mi Re Si
Do Sol Fa Si ✔
Do Fa Re La
Mission 4 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances understanding.
Continue
missions
Retrieve all band members!
Mission 5
Complete!
Complete!
Complete!
Complete!
mission 5
ANSWER CORRECTLY
And finally, a quick general knowledge test. If you fail, get ready to receive a tomato splash!
Start
mission 5
1/3
Hint: A great title
Did you know that multimedia content is essential to achieve a WOW effect in your creations?
Write here an incorrect answer
Write here the correct answer ✔
Write here an answer incorrect
mission 5
Hint: A great title
2/3
It is clear that, when making purchases, the emotional component is a driving force, but what is the exact percentage of purchases we make thanks to emotions?
Enter the correct answer ✔
Enter an incorrect answer
Enter an incorrect answer
mission 5
3/3
Hint: A great title
What percentage of our brain is involved in processing visual stimuli, like images, interactivities, or animation?
Write here an incorrect answer
Write here an incorrect answer
Write here the correct answer ✔
Mission 5 COMPLETE
Congratulations!
Take advantage of this space to write a brief explanation that develops the correct answer and enhances its understanding.
Continue
THE BAND IS COMPLETE
That sounds great! 😊
Here you can include a text to congratulateand wish luck to your audience at the end.
Boo!
Here you can include a great message indicating to the audience that they have not guessed the answer
Try again!
<div style="width: 100%; max-width: 550px; background: #b22222; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #8b0000;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button> <button class="mode-btn" id="btnWind" style="padding:10px; background:#1abc9c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🌬️ VENT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.2); padding:15px; border-radius:10px;"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'wind') { const bufferSize = 2 * audioCtx.sampleRate, buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate), output = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; const filter = audioCtx.createBiquadFilter(); lfo = audioCtx.createOscillator(); const lfoGain = audioCtx.createGain(); filter.type = "lowpass"; filter.frequency.value = 400; filter.Q.value = 10; lfo.frequency.value = 0.2; lfoGain.gain.value = 300; lfo.connect(lfoGain); lfoGain.connect(filter.frequency); source.connect(filter); filter.connect(ana); lfo.start(); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3 : 8; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (['piano', 'diapason', 'wind'].includes(mode)) ? 'none' : 'block'; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } btnPlay.onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; updateMode('piano'); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let y = (dataWave[i] / 256) * cW.height; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; let p = (currentFreq / 1500) * cF.width; if (currentType === 'wind') { for(let i=0; i<cF.width; i+=4) { let h = (Math.random() * 40 + 5) * (1 - i/cF.width); ctxF.fillRect(i, cF.height - h - 10, 2, h); } } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let posX = p * i; if (posX < cF.width) ctxF.fillRect(posX, cF.height - (60/i) - 10, 3, 60/i); } } else if (currentType === 'diapason') { let pD = (440 / 1500) * cF.width; ctxF.fillRect(pD, cF.height - 70, 4, 60); } else { ctxF.fillRect(p, cF.height - 70, 4, 60); } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3 : 8; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block'; if (mode === 'diapason') currentFreq = 440; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } btnPlay.onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, 'piano'); draw(); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); let rms = 0; for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128); let hFactor = (rms / dataWave.length) * 15; // SEUIL DE SÉCURITÉ : Si le son est trop faible, on n'affiche rien if (hFactor < 0.05) hFactor = 0; offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let y = ( (dataWave[index]-128) * Math.min(1, hFactor) + 128 ) / 256 * cW.height; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); if (hFactor > 0) { // On ne dessine le spectre que s'il y a du son ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { let h = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - h - 10, 2, h); } } else { let p = (currentFreq / 1500) * cF.width; let hBase = Math.min(80, hFactor * 80); if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, cF.height - hBase - 10, 4, hBase); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let hHarm = hBase / i; ctxF.fillRect(p*i, cF.height - hHarm - 10, 3, hHarm); } } else { ctxF.fillRect(p, cF.height - hBase - 10, 3, hBase); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hBase/2) - 10, 2, hBase/2); ctxF.globalAlpha = 1; } } } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #2c3e50; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #34495e;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #626e6f;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#e74c3c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px;"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2; const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate); const out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; const filter = audioCtx.createBiquadFilter(); filter.type = "bandpass"; filter.frequency.value = 800; filter.Q.value = 0.7; lfo = audioCtx.createOscillator(); const lfoGain = audioCtx.createGain(); lfo.frequency.value = 0.6; lfoGain.gain.value = 400; lfo.connect(lfoGain); lfoGain.connect(filter.frequency); source.connect(filter); filter.connect(ana); lfo.start(); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3 : 8; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block'; if (mode === 'diapason') currentFreq = 440; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } btnPlay.onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; updateMode('piano'); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); // Oscilloscope ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let y = (dataWave[i] / 256) * cW.height; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); // Spectre corrigé ctxF.fillStyle = "#ffeb3b"; let p = (currentFreq / 1500) * cF.width; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { let dist = Math.abs(i - cF.width/3); let h = (Math.random() * 50 + 10) * Math.max(0.1, 1 - dist/(cF.width/1.5)); ctxF.fillRect(i, cF.height - h - 10, 2, h); } } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let hHarm = 60 / i; let posX = p * i; if (posX < cF.width) ctxF.fillRect(posX, cF.height - hHarm - 10, 3, hHarm); } } else { ctxF.fillRect(p, cF.height - 70, 4, 60); } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #2c3e50; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #34495e;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #626e6f;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; cursor:pointer;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#e74c3c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px;"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { // Bruit de base const bSize = audioCtx.sampleRate * 2; const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate); const out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; // FILTRE : Pour simuler la voix humaine (Bande passante) const filter = audioCtx.createBiquadFilter(); filter.type = "bandpass"; filter.frequency.value = 800; filter.Q.value = 0.7; // LFO : Pour créer des fluctuations (effet de murmure) lfo = audioCtx.createOscillator(); const lfoGain = audioCtx.createGain(); lfo.frequency.value = 0.6; lfoGain.gain.value = 400; lfo.connect(lfoGain); lfoGain.connect(filter.frequency); source.connect(filter); filter.connect(ana); lfo.start(); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3 : 8; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block'; if (mode === 'diapason') currentFreq = 440; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } btnPlay.onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; updateMode('piano'); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); // Oscilloscope ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let y = (dataWave[i] / 256) * cW.height; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); // Spectre ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { let dist = Math.abs(i - cF.width/3); // Concentration sur les médiums let h = (Math.random() * 50 + 10) * Math.max(0.1, 1 - dist/(cF.width/1.5)); ctxF.fillRect(i, cF.height - h - 10, 2, h); } } else { let p = (currentFreq / 1500) * cF.width; ctxF.fillRect(p, cF.height - 70, 4, 60); } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #ff0000; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #cc0000;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 2px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope (Signal temporel)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre (Fréquences)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: none; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; cursor:pointer;">🎹 PIANO</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#34495e; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🗣️ BAVARDAGE</button> <button class="mode-btn" id="btnWind" style="padding:10px; background:#1abc9c; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; cursor:pointer;">🌬️ VENT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.3); padding:15px; border-radius:10px;"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.3); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, lfo, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (lfo) { try { lfo.stop(); } catch(e){} lfo.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise' || type === 'wind') { const bufferSize = 2 * audioCtx.sampleRate, buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate), output = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; const filter = audioCtx.createBiquadFilter(); lfo = audioCtx.createOscillator(); const lfoGain = audioCtx.createGain(); if (type === 'noise') { filter.type = "bandpass"; filter.frequency.value = 800; filter.Q.value = 0.7; lfo.frequency.value = 0.6; lfoGain.gain.value = 400; } else { // RÉGLAGES DU VENT (Sifflement variable) filter.type = "lowpass"; filter.frequency.value = 400; filter.Q.value = 10; lfo.frequency.value = 0.2; lfoGain.gain.value = 300; } lfo.connect(lfoGain); lfoGain.connect(filter.frequency); source.connect(filter); filter.connect(ana); lfo.start(); source.start(); } else if (type === 'piano') { const gInst = audioCtx.createGain(); source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3.02; gInst.gain.setValueAtTime(1, audioCtx.currentTime); gInst.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 3); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); gInst.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (['piano', 'noise', 'wind'].includes(mode)) ? 'none' : 'block'; init(); audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } btnPlay.onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; updateMode('piano'); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let y = (dataWave[i] / 256) * cW.height; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; let p = (currentFreq / 1500) * cF.width; if (currentType === 'noise' || currentType === 'wind') { for(let i=0; i<cF.width; i+=4) { let h = (Math.random() * 40 + 5); if (currentType === 'wind') h *= (1 - i/cF.width); // Vent plus grave (plus de basses) ctxF.fillRect(i, cF.height - h - 10, 2, h); } } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let posX = p * i; if (posX < cF.width) ctxF.fillRect(posX, cF.height - (60/i) - 10, 3, 60/i); } } else { ctxF.fillRect(p, cF.height - 70, 4, 60); } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const btnPlay = document.getElementById('btnPlay'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3.5 : 10; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function startLab() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; draw(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block'; if (mode === 'diapason') currentFreq = 440; // ACTION DIRECTE : Active le lab et joue le son startLab(); playSound(currentFreq, currentType); } btnPlay.onclick = function() { init(); if (!isPlaying) { startLab(); playSound(currentFreq, currentType); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; startLab(); playSound(currentFreq, 'piano'); }; }); function draw() { if(!isPlaying) return; requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); let rms = 0; for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128); let hFactor = Math.min(1, (rms / dataWave.length) * 15); offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let h = hFactor * (80/i); ctxF.fillRect(p*i, cF.height - h - 10, 3, h); } } else { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1; } } } document.getElementById('btnFreqToggle').onclick = () => { const ui = document.getElementById('controlsUI'); ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; }; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); let fade = (type === 'piano') ? 3.5 : 10; if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + fade); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'piano' || mode === 'diapason' || mode === 'noise') ? 'none' : 'block'; if (mode === 'diapason') currentFreq = 440; if (isPlaying) playSound(currentFreq, currentType); } document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; // On ne vide pas les canvas ici pour garder l'image "gelée" } }; document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; document.getElementById('btnPlay').textContent = "PAUSE"; document.getElementById('btnPlay').style.background = "#e67e22"; draw(); } playSound(currentFreq, 'piano'); }; }); function draw() { if(!isPlaying) return; // Arrête le dessin si on est en pause requestAnimationFrame(draw); ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); let rms = 0; for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128); let hFactor = Math.min(1, (rms / dataWave.length) * 15); offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(i * slice, y); else ctxW.lineTo(i * slice, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) { let h = hFactor * (80/i); ctxF.fillRect(p*i, cF.height - h - 10, 3, h); } } else { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1; } } } // Setup reste des boutons document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.getElementById('btnFreqToggle').onclick = () => ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; fRange.oninput = () => { currentFreq = fRange.value; fLabel.textContent = Math.round(currentFreq); if(isPlaying) playSound(currentFreq, currentType); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; } .key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; } .key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; } .mode-btn { transition: all 0.2s; border: 2px solid transparent !important; } .mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); } input[type=range] { accent-color: #40ccff; cursor: pointer; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 10); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; } if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; } if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); } function drawStaticSpectre() { ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10); } else { let p = (currentFreq / 1500) * cF.width; ctxF.fillRect(p, 10, 4, cF.height - 20); } } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9'; }; document.querySelectorAll('.mode-btn').forEach(btn => { btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()); }); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; document.getElementById('btnPlay').textContent = "PAUSE"; document.getElementById('btnPlay').style.background = "#e67e22"; } playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; this.style.background = isMuted ? "#e74c3c" : "#ecf0f1"; this.style.color = isMuted ? "white" : "#2c3e50"; if(gainNode) { gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); } }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); // Calcul de l'amplitude moyenne (RMS) pour l'extinction visuelle let rms = 0; for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128); let hFactor = Math.min(1, (rms / dataWave.length) * 15); offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; // L'oscillo suit aussi l'amplitude let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80); } else if (currentType === 'piano') { // Les harmoniques du piano suivent maintenant l'extinction for(let i=1; i<=4; i++) { let hHeight = hFactor * (80/i); ctxF.fillRect(p*i, cF.height - hHeight - 10, 3, hHeight); } } else { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1.0; } } } } draw(); updateMode('sine'); </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; } .key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; } .key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; } .mode-btn { transition: all 0.2s; border: 2px solid transparent !important; } .mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); } input[type=range] { accent-color: #40ccff; cursor: pointer; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(1, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; } if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; } if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); } function drawStaticSpectre() { ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10); } else { let p = (currentFreq / 1500) * cF.width; ctxF.fillRect(p, 10, 4, cF.height - 20); } } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9'; }; document.querySelectorAll('.mode-btn').forEach(btn => { btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()); }); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; document.getElementById('btnPlay').textContent = "PAUSE"; document.getElementById('btnPlay').style.background = "#e67e22"; } playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; this.style.background = isMuted ? "#e74c3c" : "#ecf0f1"; this.style.color = isMuted ? "white" : "#2c3e50"; if(gainNode) { gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); } }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } } draw(); updateMode('sine'); </script> </div>
<div style="width: 100%; max-width: 550px; background: #e74c3c; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #c0392b;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔊</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; } .key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; } .key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; } .mode-btn { transition: all 0.2s; border: 2px solid transparent !important; } .mode-btn.active-mode { border: 2px solid white !important; transform: scale(1.05); } input[type=range] { accent-color: #40ccff; cursor: pointer; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.01); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano' || type === 'diapason') { const gInst = audioCtx.createGain(); gInst.gain.setValueAtTime(1, audioCtx.currentTime); if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gInst); sPiano2.connect(gInst); sPiano3.connect(gInst); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = 'sine'; source.frequency.value = 440; gInst.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 10); source.connect(gInst); source.start(); } gInst.connect(ana); } else { source = audioCtx.createOscillator(); source.type = type; source.frequency.value = freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active-mode')); const activeBtn = document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)); if(activeBtn) activeBtn.classList.add('active-mode'); document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; } if (mode === 'diapason') { currentFreq = 440; fLabel.textContent = "440"; fRange.value = 440; } if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); } function drawStaticSpectre() { ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) ctxF.fillRect(i, cF.height - 20, 2, 10); } else { let p = (currentFreq / 1500) * cF.width; ctxF.fillRect(p, 10, 4, cF.height - 20); } } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9'; }; document.querySelectorAll('.mode-btn').forEach(btn => { btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()); }); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; document.getElementById('btnPlay').textContent = "PAUSE"; document.getElementById('btnPlay').style.background = "#e67e22"; } playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; playSound(currentFreq, currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); if (isPlaying) playSound(currentFreq, currentType); else drawStaticSpectre(); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔇" : "🔊"; this.style.background = isMuted ? "#e74c3c" : "#ecf0f1"; this.style.color = isMuted ? "white" : "#2c3e50"; if(gainNode) { gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); } }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; let rms = 0; // On calcule l'amplitude pour raccorder la hauteur du spectre for(let i=0; i<dataWave.length; i++) rms += Math.abs(dataWave[i]-128); let hFactor = Math.min(1, (rms / dataWave.length) * 15); if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 4, hFactor*80); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, cF.height - (hFactor*(80/i)) - 10, 3, hFactor*(80/i)); } else { ctxF.fillRect(p, cF.height - (hFactor*80) - 10, 3, hFactor*80); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, cF.height - (hFactor*40) - 10, 2, hFactor*40); ctxF.globalAlpha = 1.0; } } } } draw(); updateMode('sine'); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1.5; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">🔇</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;"> <div style="color:#3498db; font-size:11px; margin-bottom:8px;">Notes Fixes : <span id="pianoNote">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: none; flex-direction: column; gap: 10px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px;">Fréquence Libre : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; } .key.active { background: #3498db; color: white; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function togglePlayState() { if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; const b = document.getElementById('btnPlay'); b.textContent = "PAUSE"; b.style.background = "#e67e22"; draw(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; // Affichage piano document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; // Logique de masquage auto du curseur fréquence const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; // Si c'est un instrument, on ferme les réglages s'ils étaient ouverts (sauf volume) if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#34495e'; } if (mode === 'diapason') currentFreq = 440; if (isPlaying) playSound(currentFreq, currentType); } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#34495e' : '#3498db'; }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; togglePlayState(); playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); togglePlayState(); if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') { playSound(currentFreq, currentType); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔊" : "🔇"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; } ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#f1c40f"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } draw(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #5d1919; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 1px solid #7d2a2a;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 2px solid #222; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 2px solid #222; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; transition: transform 0.1s;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #95a5a6; border: none; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 16px;">🔇</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ecf0f1; border:none; border-radius:6px; color:#2c3e50; font-size:11px; font-weight:bold;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#2c3e50; font-size:11px; font-weight:bold;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#7f8c8d; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.3); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.1);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Clavier : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.4); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.1);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%; cursor: pointer;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; cursor: pointer;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; } .key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; } .key.active { background: #3498db; color: white; box-shadow: 0 4px #2980b9; } input[type=range] { accent-color: #3498db; } .mode-btn:hover { opacity: 0.9; transform: scale(1.02); } .mode-btn:active { transform: scale(0.98); } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function togglePlayState() { if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; const b = document.getElementById('btnPlay'); b.textContent = "PAUSE"; b.style.background = "#e67e22"; draw(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.border = 'none'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.border = '2px solid white'; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; } if (mode === 'diapason') currentFreq = 440; if (isPlaying) playSound(currentFreq, currentType); } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9'; }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; togglePlayState(); playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); togglePlayState(); if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') { playSound(currentFreq, currentType); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔊" : "🔇"; this.style.background = isMuted ? "#e74c3c" : "#95a5a6"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; } ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } draw(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #a51212; border-radius: 16px; padding: 20px; color: #f5f6fa; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; text-align: center; box-shadow: 0 15px 35px rgba(0,0,0,0.6); border: 2px solid #7a0d0d;"> <h3 style="margin: 0 0 15px 0; color: #ffffff; font-size: 18px; text-transform: uppercase; letter-spacing: 1.5px; text-shadow: 1px 1px 3px rgba(0,0,0,0.3);">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #40ccff; text-align: left; margin-bottom: 3px; font-weight: bold; text-transform: uppercase;">Oscilloscope</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="font-size: 10px; color: #ffeb3b; text-align: left; margin: 10px 0 3px 0; font-weight: bold; text-transform: uppercase;">Spectre</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 85px; background: #000; border: 3px solid #1a1a1a; border-radius: 6px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1.5; background: #2ecc71; border: 1px solid #27ae60; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 4px 0 #219150;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #4b5d67; border: 1px solid #3c4a52; padding: 12px; color: white; border-radius: 8px; cursor: pointer; font-size: 13px; box-shadow: 0 4px 0 #2c3e50;">⚙️ RÉGLAGES</button> <button id="btnMute" style="flex: 0.6; background: #ecf0f1; border: 1px solid #bdc3c7; padding: 12px; color: #2c3e50; border-radius: 8px; cursor: pointer; font-size: 16px; box-shadow: 0 4px 0 #95a5a6;">🔇</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;"> <button class="mode-btn" id="btnSine" style="padding:10px; background:#3498db; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #2980b9;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:10px; background:#e67e22; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #d35400;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:10px; background:#9b59b6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #8e44ad;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:10px; background:#ffffff; border:none; border-radius:6px; color:#c0392b; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #bdc3c7;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:10px; background:#f1c40f; border:none; border-radius:6px; color:#7f4f24; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #f39c12;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:10px; background:#95a5a6; border:none; border-radius:6px; color:white; font-size:11px; font-weight:bold; box-shadow: 0 3px 0 #7f8c8d;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 12px; background:rgba(0,0,0,0.5); padding:15px; border-radius:10px; border: 1px solid rgba(255,255,255,0.2);"> <div style="color:#ffffff; font-size:11px; margin-bottom:10px; font-weight:bold;">Note : <span id="pianoNote" style="color:#40ccff;">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: rgba(0,0,0,0.5); padding: 15px; border-radius: 10px; display: none; flex-direction: column; gap: 12px; border: 1px solid rgba(255,255,255,0.2);"> <div id="freqContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Fréquence : <span id="fLabel" style="color: #40ccff; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1500" value="440" style="width: 100%;"> </div> <div id="volContainer"> <label style="display: block; font-size: 12px; margin-bottom: 5px;">Volume : <span id="vLabel" style="color: #ffeb3b; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 0; width: 42px; border-radius: 0 0 5px 5px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 4px #ccc; transition: 0.1s; } .key:active { transform: translateY(2px); box-shadow: 0 2px #ccc; } .key.active { background: #3498db; color: white; box-shadow: 0 4px #217dbb; } .mode-btn:active { transform: translateY(2px); box-shadow: none !important; } #btnPlay:active { transform: translateY(2px); box-shadow: none !important; } input[type=range] { accent-color: #40ccff; cursor: pointer; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const ui = document.getElementById('controlsUI'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function togglePlayState() { if (!isPlaying) { init(); audioCtx.resume(); isPlaying = true; const b = document.getElementById('btnPlay'); b.textContent = "PAUSE"; b.style.background = "#e67e22"; b.style.boxShadow = "0 4px 0 #d35400"; draw(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.border = 'none'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.border = '2px solid white'; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; const isInstrument = (mode === 'piano' || mode === 'diapason' || mode === 'noise'); document.getElementById('freqContainer').style.display = isInstrument ? 'none' : 'block'; if (isInstrument) { ui.style.display = 'none'; document.getElementById('btnFreqToggle').style.background = '#4b5d67'; } if (mode === 'diapason') currentFreq = 440; if (isPlaying) playSound(currentFreq, currentType); } document.getElementById('btnFreqToggle').onclick = function() { ui.style.display = (ui.style.display === 'none') ? 'flex' : 'none'; this.style.background = (ui.style.display === 'none') ? '#4b5d67' : '#2980b9'; }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; togglePlayState(); playSound(currentFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; this.style.boxShadow = "0 4px 0 #d35400"; updateMode(currentType); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#2ecc71"; this.style.boxShadow = "0 4px 0 #219150"; } }; fRange.oninput = () => { currentFreq = parseFloat(fRange.value); fLabel.textContent = Math.round(currentFreq); togglePlayState(); if (currentType !== 'noise' && currentType !== 'piano' && currentType !== 'diapason') { playSound(currentFreq, currentType); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔊" : "🔇"; this.style.background = isMuted ? "#e74c3c" : "#ecf0f1"; this.style.color = isMuted ? "white" : "#2c3e50"; this.style.boxShadow = isMuted ? "0 4px 0 #c0392b" : "0 4px 0 #95a5a6"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; } ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#40ccff"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#ffeb3b"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let p = (currentFreq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } draw(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;"> <div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div> <label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; } .key.active { background: #3498db; color: white; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440; let offset = 0; let lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block'; if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode); } document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentPianoFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; // Si on clique sur une touche piano alors qu'on est en "Pause", on démarre automatiquement le son if(!isPlaying) { isPlaying = true; const b = document.getElementById('btnPlay'); b.textContent = "PAUSE"; b.style.background = "#e67e22"; audioCtx.resume(); } playSound(currentPianoFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; fRange.oninput = () => { fLabel.textContent = fRange.value; if (isPlaying && source && !sPiano2 && currentType !== 'diapason') { source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; } ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#f1c40f"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value); let p = (freq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } draw(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1.5; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button> <button id="btnFreqToggle" style="flex: 1; background: #34495e; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">⚙️ FRÉQUENCE</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-size: 12px;">🔇</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;"> <div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;"> <div id="freqContainer" style="display: block;"> <label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div> <label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; } .key.active { background: #3498db; color: white; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440; let offset = 0, lastNoiseHeights = []; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); for(let i=0; i<cF.width; i+=4) lastNoiseHeights.push(10); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function togglePlayState(forcePlay = true) { if (forcePlay && !isPlaying) { init(); audioCtx.resume(); isPlaying = true; const b = document.getElementById('btnPlay'); b.textContent = "PAUSE"; b.style.background = "#e67e22"; } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; // On cache aussi la fréquence auto pour piano/diapason/noise document.getElementById('freqContainer').style.opacity = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? '0.3' : '1'; document.getElementById('freqContainer').style.pointerEvents = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'auto'; if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode); } document.getElementById('btnFreqToggle').onclick = function() { const fc = document.getElementById('freqContainer'); fc.style.display = (fc.style.display === 'none') ? 'block' : 'none'; this.style.background = (fc.style.display === 'none') ? '#2c3e50' : '#34495e'; }; document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentPianoFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; togglePlayState(); playSound(currentPianoFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; fRange.oninput = () => { fLabel.textContent = fRange.value; togglePlayState(); if (source && !sPiano2 && currentType !== 'diapason' && currentType !== 'noise') { source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05); } else { updateMode(currentType); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "🔊" : "🔇"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2.5) % 200; } ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#f1c40f"; if (currentType === 'noise') { for(let i=0; i<cF.width; i+=4) { if (isPlaying) lastNoiseHeights[i/4] = 10 + Math.random() * 50; ctxF.fillRect(i, cF.height - lastNoiseHeights[i/4] - 10, 2, lastNoiseHeights[i/4]); } } else { let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value); let p = (freq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } } draw(); </script> </div>
<div style="width: 100%; max-width: 500px; background: #2c3e50; border-radius: 15px; padding: 20px; color: white; font-family: sans-serif; text-align: center; box-shadow: 0 4px 15px rgba(0,0,0,0.3);"> <h3 style="margin-top: 0; font-size: 18px;">Visualiseur de Son</h3> <canvas id="oscillo" style="width: 100%; height: 120px; background: #1a1a1a; border-radius: 8px; border: 1px solid #444;"></canvas> <div style="margin: 20px 0;"> <button id="btnPlay" style="background: #e74c3c; border: none; padding: 10px 25px; color: white; border-radius: 50px; cursor: pointer; font-weight: bold; transition: 0.3s;"> DÉMARRER SON </button> </div> <div style="font-size: 14px; opacity: 0.8;"> Fréquence : <span id="fLabel">440</span> Hz <input type="range" id="fRange" min="200" max="800" value="440" style="width: 100%; margin-top: 10px; accent-color: #3498db;"> </div> <script> let audioCtx, osc, ana, data; let playing = false; const btn = document.getElementById('btnPlay'); const canvas = document.getElementById('oscillo'); const ctx = canvas.getContext('2d'); const range = document.getElementById('fRange'); const label = document.getElementById('fLabel'); function setup() { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); osc = audioCtx.createOscillator(); ana = audioCtx.createAnalyser(); ana.fftSize = 2048; osc.connect(ana); ana.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); osc.start(); } btn.onclick = () => { if (!audioCtx) setup(); if (!playing) { audioCtx.resume(); btn.textContent = "STOP"; btn.style.background = "#27ae60"; playing = true; draw(); } else { audioCtx.suspend(); btn.textContent = "DÉMARRER SON"; btn.style.background = "#e74c3c"; playing = false; } }; function draw() { if (!playing) return; requestAnimationFrame(draw); osc.frequency.value = range.value; label.textContent = range.value; ana.getByteTimeDomainData(data); ctx.fillStyle = "#1a1a1a"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let sliceWidth = canvas.width / data.length; let x = 0; for (let i = 0; i < data.length; i++) { let v = data[i] / 128.0; let y = v * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += sliceWidth; } ctx.stroke(); } </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 15px 0; color: #3498db;">Laboratoire d'Ondes Sonores</h3> <canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: flex; gap: 5px; margin-bottom: 15px;"> <button id="btnSine" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-weight:bold;">SINUS</button> <button id="btnSquare" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">CARRÉ</button> <button id="btnTriangle" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">TRIANGLE</button> </div> <div style="background: #2c3e50; padding: 15px; border-radius: 8px;"> <label style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;"> </div> <script> let audioCtx, osc, ana, gainNode, data; let isPlaying = false; let isMuted = false; const btnPlay = document.getElementById('btnPlay'); const btnMute = document.getElementById('btnMute'); const btnSine = document.getElementById('btnSine'); const btnSquare = document.getElementById('btnSquare'); const btnTriangle = document.getElementById('btnTriangle'); const canvas = document.getElementById('oscillo'); const ctx = canvas.getContext('2d'); const range = document.getElementById('fRange'); const label = document.getElementById('fLabel'); function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); osc = audioCtx.createOscillator(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; osc.type = 'sine'; osc.frequency.setValueAtTime(range.value, audioCtx.currentTime); osc.connect(ana); ana.connect(gainNode); gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); osc.start(); } } // Gestionnaire de changement d'onde function updateWave(type) { if (!audioCtx) initAudio(); if (osc) osc.type = type; // Mise à jour visuelle des boutons [btnSine, btnSquare, btnTriangle].forEach(btn => btn.style.background = '#444'); if(type === 'sine') btnSine.style.background = '#3498db'; if(type === 'square') btnSquare.style.background = '#3498db'; if(type === 'triangle') btnTriangle.style.background = '#3498db'; } // Ecouteurs d'événements (plus fiables que onclick dans le HTML) btnSine.addEventListener('click', () => updateWave('sine')); btnSquare.addEventListener('click', () => updateWave('square')); btnTriangle.addEventListener('click', () => updateWave('triangle')); btnPlay.onclick = () => { initAudio(); if (!isPlaying) { audioCtx.resume(); btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; isPlaying = true; draw(); } else { audioCtx.suspend(); btnPlay.textContent = "DÉMARRER"; btnPlay.style.background = "#27ae60"; isPlaying = false; } }; btnMute.onclick = () => { if (!audioCtx) initAudio(); isMuted = !isMuted; gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.02); btnMute.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; btnMute.style.background = isMuted ? "#c0392b" : "#7f8c8d"; }; function draw() { if (!isPlaying) return; requestAnimationFrame(draw); osc.frequency.value = range.value; label.textContent = range.value; ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let sliceWidth = canvas.width / data.length; let x = 0; for (let i = 0; i < data.length; i++) { let v = data[i] / 128.0; let y = v * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += sliceWidth; } ctx.stroke(); } </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 15px 0; color: #3498db;">Laboratoire d'Ondes Sonores</h3> <canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: flex; gap: 5px; margin-bottom: 15px;"> <button id="btnSine" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-weight:bold;">SINUS</button> <button id="btnSquare" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">CARRÉ</button> <button id="btnTriangle" style="flex:1; padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-weight:bold;">TRIANGLE</button> </div> <div style="background: #2c3e50; padding: 15px; border-radius: 8px;"> <label style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;"> </div> <script> let audioCtx, osc, ana, gainNode, data; let isPlaying = false; let isMuted = false; let currentType = 'sine'; const btnPlay = document.getElementById('btnPlay'); const btnMute = document.getElementById('btnMute'); const btnSine = document.getElementById('btnSine'); const btnSquare = document.getElementById('btnSquare'); const btnTriangle = document.getElementById('btnTriangle'); const canvas = document.getElementById('oscillo'); const ctx = canvas.getContext('2d'); const range = document.getElementById('fRange'); const label = document.getElementById('fLabel'); function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); osc = audioCtx.createOscillator(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; osc.type = currentType; osc.frequency.setValueAtTime(range.value, audioCtx.currentTime); osc.connect(ana); ana.connect(gainNode); gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); osc.start(); } } function updateWave(type) { currentType = type; if (osc) osc.type = type; [btnSine, btnSquare, btnTriangle].forEach(btn => btn.style.background = '#444'); document.getElementById('btn' + type.charAt(0).toUpperCase() + type.slice(1)).style.background = '#3498db'; // Si on est en pause, on force un dessin statique pour montrer le changement if (!isPlaying) { drawStatic(type); } } // Fonction pour dessiner l'onde même quand le son est coupé function drawStatic(type) { ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let freqFactor = range.value / 100; for (let x = 0; x < canvas.width; x++) { let angle = (x / canvas.width) * Math.PI * 2 * freqFactor; let y; if (type === 'sine') y = Math.sin(angle); else if (type === 'square') y = Math.sin(angle) >= 0 ? 1 : -1; else if (type === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(angle)); let posY = (y * canvas.height / 3) + (canvas.height / 2); if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY); } ctx.stroke(); } btnSine.onclick = () => updateWave('sine'); btnSquare.onclick = () => updateWave('square'); btnTriangle.onclick = () => updateWave('triangle'); btnPlay.onclick = () => { initAudio(); if (!isPlaying) { audioCtx.resume(); btnPlay.textContent = "PAUSE"; btnPlay.style.background = "#e67e22"; isPlaying = true; draw(); } else { audioCtx.suspend(); btnPlay.textContent = "DÉMARRER"; btnPlay.style.background = "#27ae60"; isPlaying = false; } }; btnMute.onclick = () => { if (!audioCtx) initAudio(); isMuted = !isMuted; gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.02); btnMute.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; btnMute.style.background = isMuted ? "#c0392b" : "#7f8c8d"; }; range.oninput = () => { label.textContent = range.value; if (osc) osc.frequency.value = range.value; if (!isPlaying) drawStatic(currentType); }; function draw() { if (!isPlaying) return; requestAnimationFrame(draw); ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let sliceWidth = canvas.width / data.length; let x = 0; for (let i = 0; i < data.length; i++) { let v = data[i] / 128.0; let y = v * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += sliceWidth; } ctx.stroke(); } // Premier dessin à vide drawStatic('sine'); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 20px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 15px 0; color: #3498db;">Analyseur de Sons Complexes</h3> <canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 10px; margin: 15px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 12px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px; margin-bottom: 10px;"> <button id="btnSine" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#3498db; color:white; font-size:11px;">SINUS</button> <button id="btnSquare" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px;">CARRÉ</button> <button id="btnTriangle" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px;">TRIANGLE</button> <button id="btnPiano" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px; font-weight:bold;">🎹 PIANO</button> <button id="btnNoise" style="padding:8px; border:none; border-radius:4px; cursor:pointer; background:#444; color:white; font-size:11px; font-weight:bold;">💨 BRUIT</button> </div> <div style="background: #2c3e50; padding: 15px; border-radius: 8px;"> <label id="labelType" style="display: block; margin-bottom: 5px; font-size: 14px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; cursor: pointer; accent-color: #3498db;"> </div> <script> let audioCtx, osc, ana, gainNode, data, noiseNode; let isPlaying = false, isMuted = false, currentType = 'sine'; const notes = { 261:"DO", 293:"RÉ", 329:"MI", 349:"FA", 392:"SOL", 440:"LA", 493:"SI" }; const buttons = { sine: document.getElementById('btnSine'), square: document.getElementById('btnSquare'), triangle: document.getElementById('btnTriangle'), piano: document.getElementById('btnPiano'), noise: document.getElementById('btnNoise') }; const canvas = document.getElementById('oscillo'); const ctx = canvas.getContext('2d'); const range = document.getElementById('fRange'); const label = document.getElementById('fLabel'); const labelType = document.getElementById('labelType'); function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); createSource(); } } function createSource() { if (osc) { osc.stop(); osc.disconnect(); } if (noiseNode) { noiseNode.stop(); noiseNode.disconnect(); } if (currentType === 'noise') { const bufferSize = 2 * audioCtx.sampleRate, buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate), output = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1; noiseNode = audioCtx.createBufferSource(); noiseNode.buffer = buffer; noiseNode.loop = true; noiseNode.connect(ana); noiseNode.start(); } else if (currentType === 'piano') { // Synthèse additive simple pour le timbre piano osc = audioCtx.createOscillator(); osc.type = 'triangle'; const osc2 = audioCtx.createOscillator(); // Harmonique 2 osc2.type = 'sine'; const g2 = audioCtx.createGain(); g2.gain.value = 0.5; osc.connect(ana); osc2.connect(g2); g2.connect(ana); osc.frequency.value = range.value; osc2.frequency.value = range.value * 2; osc.start(); osc2.start(); // On stocke le 2eme osc dans une propriété pour le couper plus tard osc.slave = osc2; } else { osc = audioCtx.createOscillator(); osc.type = currentType; osc.frequency.value = range.value; osc.connect(ana); osc.start(); } ana.connect(gainNode); } function updateMode(type) { currentType = type; Object.values(buttons).forEach(b => b.style.background = '#444'); buttons[type].style.background = '#3498db'; if (type === 'piano') { range.min = 261; range.max = 493; range.step = 1; labelType.innerHTML = "Note : <span id='fLabel' style='color:#3498db;font-weight:bold;'>LA</span>"; } else if (type === 'noise') { labelType.innerHTML = "Intensité du bruit"; } else { range.min = 100; range.max = 1000; range.step = 1; labelType.innerHTML = "Fréquence : <span id='fLabel' style='color:#3498db;font-weight:bold;'>440</span> Hz"; } if (audioCtx) createSource(); if (!isPlaying) drawStatic(); } Object.keys(buttons).forEach(key => { buttons[key].onclick = () => updateMode(key); }); document.getElementById('btnPlay').onclick = function() { initAudio(); if (!isPlaying) { audioCtx.resume(); this.textContent = "PAUSE"; this.style.background = "#e67e22"; isPlaying = true; draw(); } else { audioCtx.suspend(); this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; isPlaying = false; } }; range.oninput = () => { if (currentType === 'piano') { let closest = Object.keys(notes).reduce((a, b) => Math.abs(b - range.value) < Math.abs(a - range.value) ? b : a); document.getElementById('fLabel').textContent = notes[closest]; if (osc) { osc.frequency.setTargetAtTime(closest, audioCtx.currentTime, 0.05); if(osc.slave) osc.slave.frequency.setTargetAtTime(closest*2, audioCtx.currentTime, 0.05); } } else { document.getElementById('fLabel').textContent = range.value; if (osc) osc.frequency.value = range.value; } if (!isPlaying) drawStatic(); }; function drawStatic() { ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); for (let x = 0; x < canvas.width; x++) { let y = 0; let angle = (x / canvas.width) * Math.PI * 2 * (range.value / 100); if (currentType === 'sine') y = Math.sin(angle); else if (currentType === 'square') y = Math.sin(angle) >= 0 ? 1 : -1; else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(angle)); else if (currentType === 'piano') y = (Math.sin(angle) + 0.5 * Math.sin(angle * 2)); else if (currentType === 'noise') y = Math.random() * 2 - 1; let posY = (y * canvas.height / 4) + (canvas.height / 2); if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY); } ctx.stroke(); } function draw() { if (!isPlaying) return; requestAnimationFrame(draw); ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let sliceWidth = canvas.width / data.length; let x = 0; for (let i = 0; i < data.length; i++) { let v = data[i] / 128.0; let y = v * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += sliceWidth; } ctx.stroke(); } drawStatic(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire Acoustique Avancé</h3> <canvas id="oscillo" width="600" height="180" style="width: 100%; height: 140px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:10px; border-radius:8px;"> <div style="display:flex; justify-content:center; gap:2px;"> <button class="key" data-f="261.63">DO</button> <button class="key" data-f="293.66">RÉ</button> <button class="key" data-f="329.63">MI</button> <button class="key" data-f="349.23">FA</button> <button class="key" data-f="392.00">SOL</button> <button class="key" data-f="440.00">LA</button> <button class="key" data-f="493.88">SI</button> </div> </div> <div id="freqControl" style="background: #2c3e50; padding: 10px; border-radius: 8px;"> <label style="display: block; font-size: 13px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1200" value="440" style="width: 100%; accent-color: #3498db;"> </div> <style> .key { background: white; color: black; border: none; padding: 10px 5px; width: 35px; border-radius: 0 0 3px 3px; font-size: 10px; font-weight: bold; cursor: pointer; } .key:active { background: #ddd; } .key.active { background: #3498db; color: white; } </style> <script> let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine'; const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'); const pianoI = document.getElementById('pianoInterface'), freqC = document.getElementById('freqControl'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); } } function stopSource() { if (source) { try { source.stop(); } catch(e){} source.disconnect(); } } function playSound(freq, type) { init(); stopSource(); if (isMuted) gainNode.gain.value = 0; else gainNode.gain.value = 1; if (type === 'noise') { const bSize = audioCtx.sampleRate * 2; const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate); const output = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) output[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; } else if (type === 'piano') { source = audioCtx.createOscillator(); source.type = 'square'; // Base plus riche const g = audioCtx.createGain(); // Enveloppe Piano : Frappe puis extinction g.gain.setValueAtTime(0.5, audioCtx.currentTime); g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.5); source.connect(g); g.connect(ana); source.frequency.value = freq; source.start(); return; } else { source = audioCtx.createOscillator(); source.type = type === 'diapason' ? 'sine' : type; source.frequency.value = type === 'diapason' ? 440 : freq; } source.connect(ana); source.start(); } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; pianoI.style.display = (mode === 'piano') ? 'block' : 'none'; freqC.style.display = (mode === 'diapason' || mode === 'piano') ? 'none' : 'block'; if (mode === 'diapason') fLabel.textContent = "440"; if (isPlaying) playSound(fRange.value, mode); else drawStatic(); } document.querySelectorAll('.mode-btn').forEach(btn => { btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()); }); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); if(isPlaying) playSound(key.dataset.f, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; if (gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : 1, audioCtx.currentTime, 0.05); this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; }; fRange.oninput = () => { fLabel.textContent = fRange.value; if(source && isPlaying) source.frequency.value = fRange.value; if(!isPlaying) drawStatic(); }; function drawStatic() { ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let freq = currentType === 'diapason' ? 440 : fRange.value; for (let x = 0; x < canvas.width; x++) { let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100); if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a); else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7; else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a)); else if (currentType === 'piano') y = (Math.sin(a) + 0.3*Math.sin(a*2) + 0.2*Math.sin(a*3)); else if (currentType === 'noise') y = Math.random() * 2 - 1; let posY = (y * canvas.height / 4) + (canvas.height / 2); if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY); } ctx.stroke(); } function draw() { if (!isPlaying) return; requestAnimationFrame(draw); ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let slice = canvas.width / data.length, x = 0; for (let i = 0; i < data.length; i++) { let y = (data[i] / 128.0) * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += slice; } ctx.stroke(); } drawStatic(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire Acoustique : Fréquence & Amplitude</h3> <canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:10px; border-radius:8px;"> <div style="display:flex; justify-content:center; gap:2px;"> <button class="key" data-f="261.63">DO</button> <button class="key" data-f="293.66">RÉ</button> <button class="key" data-f="329.63">MI</button> <button class="key" data-f="349.23">FA</button> <button class="key" data-f="392.00">SOL</button> <button class="key" data-f="440.00">LA</button> <button class="key" data-f="493.88">SI</button> </div> </div> <div style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;"> <div id="freqContainer"> <label style="display: block; font-size: 13px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1200" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div> <label style="display: block; font-size: 13px;">Volume (Amplitude) : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 10px 5px; width: 35px; border-radius: 0 0 3px 3px; font-size: 10px; font-weight: bold; cursor: pointer; } .key.active { background: #3498db; color: white; } input[type=range] { cursor: pointer; } </style> <script> let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine'; const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'); const vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const pianoI = document.getElementById('pianoInterface'), freqContainer = document.getElementById('freqContainer'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } let vol = isMuted ? 0 : vRange.value / 100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2; const buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate); const output = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) output[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; } else if (type === 'piano') { source = audioCtx.createOscillator(); source.type = 'square'; const pianoGain = audioCtx.createGain(); pianoGain.gain.setValueAtTime(0.4, audioCtx.currentTime); pianoGain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.5); source.connect(pianoGain); pianoGain.connect(ana); source.frequency.value = freq; source.start(); return; } else { source = audioCtx.createOscillator(); source.type = type === 'diapason' ? 'sine' : type; source.frequency.value = type === 'diapason' ? 440 : freq; } source.connect(ana); source.start(); } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; pianoI.style.display = (mode === 'piano') ? 'block' : 'none'; freqContainer.style.visibility = (mode === 'diapason' || mode === 'noise') ? 'hidden' : 'visible'; if (isPlaying) playSound(fRange.value, mode); else drawStatic(); } document.querySelectorAll('.mode-btn').forEach(btn => { btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase()); }); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); if(isPlaying) playSound(key.dataset.f, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value / 100, audioCtx.currentTime, 0.02); if(!isPlaying) drawStatic(); }; fRange.oninput = () => { fLabel.textContent = fRange.value; if(source && isPlaying && currentType !== 'noise') source.frequency.value = fRange.value; if(!isPlaying) drawStatic(); }; function drawStatic() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let freq = currentType === 'diapason' ? 440 : fRange.value; let amp = vRange.value / 100; for (let x = 0; x < canvas.width; x++) { let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100); if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a); else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7; else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a)); else if (currentType === 'piano') y = (Math.sin(a) + 0.3*Math.sin(a*2)); else if (currentType === 'noise') y = Math.random() * 2 - 1; let posY = (y * amp * canvas.height / 2.5) + (canvas.height / 2); if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY); } ctx.stroke(); } function draw() { if (!isPlaying) return; requestAnimationFrame(draw); ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let slice = canvas.width / data.length, x = 0; for (let i = 0; i < data.length; i++) { let y = (data[i] / 128.0) * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += slice; } ctx.stroke(); } drawStatic(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 18px;">Laboratoire : Analyse des Sons</h3> <canvas id="oscillo" width="600" height="200" style="width: 100%; height: 150px; background: #000; border: 2px solid #34495e; border-radius: 5px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON (440Hz)</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;"> <div style="color:#3498db; font-size:12px; margin-bottom:10px;">Fondamentale : <span id="pianoNote">---</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;"> <div id="freqContainer"> <label style="display: block; font-size: 13px;">Fréquence Fondamentale : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div> <label style="display: block; font-size: 13px;">Amplitude (Volume) : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 15px 5px; width: 40px; border-radius: 0 0 4px 4px; font-size: 11px; font-weight: bold; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .key:active, .key.active { background: #3498db; color: white; transform: translateY(2px); } </style> <script> let audioCtx, ana, gainNode, data, source, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440; const canvas = document.getElementById('oscillo'), ctx = canvas.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); const pianoI = document.getElementById('pianoInterface'), controlsUI = document.getElementById('controlsUI'), freqContainer = document.getElementById('freqContainer'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); data = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; } else if (type === 'piano') { source = audioCtx.createOscillator(); source.type = 'triangle'; const s2 = audioCtx.createOscillator(); s2.type = 'sine'; s2.frequency.value = freq * 2; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(0.5, audioCtx.currentTime); gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1.2); source.connect(gP); s2.connect(gP); gP.connect(ana); source.frequency.value = freq; source.start(); s2.start(); source.onended = () => { s2.stop(); s2.disconnect(); }; return; } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; } source.connect(ana); source.start(); } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; pianoI.style.display = (mode === 'piano') ? 'block' : 'none'; freqContainer.style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block'; if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode); drawStatic(); } document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentPianoFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if(isPlaying) playSound(currentPianoFreq, 'piano'); else drawStatic(); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); drawStatic(); }; fRange.oninput = () => { fLabel.textContent = fRange.value; drawStatic(); }; function drawStatic() { ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 2; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value); let amp = vRange.value / 100; for (let x = 0; x < canvas.width; x++) { let y = 0, a = (x / canvas.width) * Math.PI * 2 * (freq / 100); if (currentType === 'sine' || currentType === 'diapason') y = Math.sin(a); else if (currentType === 'square') y = Math.sin(a) >= 0 ? 0.7 : -0.7; else if (currentType === 'triangle') y = (2 / Math.PI) * Math.asin(Math.sin(a)); else if (currentType === 'piano') y = (Math.sin(a) + 0.4 * Math.sin(a * 2)); // Fondamentale + Harmonique else if (currentType === 'noise') y = Math.random() * 2 - 1; let posY = (y * amp * canvas.height / 2.5) + (canvas.height / 2); if (x === 0) ctx.moveTo(x, posY); else ctx.lineTo(x, posY); } ctx.stroke(); } function draw() { if (!isPlaying) return; requestAnimationFrame(draw); ana.getByteTimeDomainData(data); ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 3; ctx.strokeStyle = "#3498db"; ctx.beginPath(); let slice = canvas.width / data.length, x = 0; for (let i = 0; i < data.length; i++) { let y = (data[i] / 128.0) * canvas.height / 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); x += slice; } ctx.stroke(); } drawStatic(); </script> </div>
<div style="width: 100%; max-width: 550px; background: #1e272e; border-radius: 12px; padding: 15px; color: #ecf0f1; font-family: sans-serif; text-align: center; box-shadow: 0 10px 20px rgba(0,0,0,0.5);"> <h3 style="margin: 0 0 10px 0; color: #3498db; font-size: 16px;">Laboratoire Acoustique</h3> <div style="font-size: 10px; color: #3498db; text-align: left; margin-bottom: 2px;">Oscilloscope (Forme de l'onde)</div> <canvas id="oscillo" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="font-size: 10px; color: #f1c40f; text-align: left; margin: 8px 0 2px 0;">Spectre (Analyse fréquentielle)</div> <canvas id="spectre" width="600" height="100" style="width: 100%; height: 80px; background: #000; border: 1px solid #34495e; border-radius: 4px;"></canvas> <div style="display: flex; gap: 8px; margin: 10px 0;"> <button id="btnPlay" style="flex: 1; background: #27ae60; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">DÉMARRER</button> <button id="btnMute" style="flex: 1; background: #7f8c8d; border: none; padding: 10px; color: white; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 12px;">COUPER SON</button> </div> <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 10px;"> <button class="mode-btn" id="btnSine" style="padding:6px; background:#3498db; border:none; border-radius:4px; color:white; font-size:10px;">SINUS</button> <button class="mode-btn" id="btnSquare" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">CARRÉ</button> <button class="mode-btn" id="btnTriangle" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">TRIANGLE</button> <button class="mode-btn" id="btnPiano" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🎹 PIANO</button> <button class="mode-btn" id="btnDiapason" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">🔔 DIAPASON</button> <button class="mode-btn" id="btnNoise" style="padding:6px; background:#444; border:none; border-radius:4px; color:white; font-size:10px;">💨 BRUIT</button> </div> <div id="pianoInterface" style="display:none; margin-bottom: 10px; background:#000; padding:15px; border-radius:8px;"> <div style="color:#3498db; font-size:11px; margin-bottom:8px;">Note : <span id="pianoNote">LA (440Hz)</span></div> <div style="display:flex; justify-content:center; gap:4px;"> <button class="key" data-f="261.63" data-n="DO (261Hz)">DO</button> <button class="key" data-f="293.66" data-n="RÉ (293Hz)">RÉ</button> <button class="key" data-f="329.63" data-n="MI (329Hz)">MI</button> <button class="key" data-f="349.23" data-n="FA (349Hz)">FA</button> <button class="key" data-f="392.00" data-n="SOL (392Hz)">SOL</button> <button class="key active" data-f="440.00" data-n="LA (440Hz)">LA</button> <button class="key" data-f="493.88" data-n="SI (493Hz)">SI</button> </div> </div> <div id="controlsUI" style="background: #2c3e50; padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px;"> <div id="freqContainer"> <label style="display: block; font-size: 12px;">Fréquence : <span id="fLabel" style="color: #3498db; font-weight: bold;">440</span> Hz</label> <input type="range" id="fRange" min="100" max="1000" value="440" style="width: 100%; accent-color: #3498db;"> </div> <div> <label style="display: block; font-size: 12px;">Volume : <span id="vLabel" style="color: #f1c40f; font-weight: bold;">50</span> %</label> <input type="range" id="vRange" min="0" max="100" value="50" style="width: 100%; accent-color: #f1c40f;"> </div> </div> <style> .key { background: white; color: black; border: none; padding: 12px 5px; width: 38px; border-radius: 0 0 4px 4px; font-size: 10px; font-weight: bold; cursor: pointer; } .key.active { background: #3498db; color: white; } </style> <script> let audioCtx, ana, gainNode, dataWave, source, sPiano2, sPiano3, isPlaying = false, isMuted = false, currentType = 'sine', currentPianoFreq = 440; let offset = 0; const cW = document.getElementById('oscillo'), ctxW = cW.getContext('2d'); const cF = document.getElementById('spectre'), ctxF = cF.getContext('2d'); const fRange = document.getElementById('fRange'), fLabel = document.getElementById('fLabel'), vRange = document.getElementById('vRange'), vLabel = document.getElementById('vLabel'); function init() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); ana = audioCtx.createAnalyser(); gainNode = audioCtx.createGain(); ana.fftSize = 2048; ana.connect(gainNode); gainNode.connect(audioCtx.destination); dataWave = new Uint8Array(ana.frequencyBinCount); } } function playSound(freq, type) { init(); if (source) { try { source.stop(); } catch(e){} source.disconnect(); } if (sPiano2) { try { sPiano2.stop(); } catch(e){} sPiano2.disconnect(); } if (sPiano3) { try { sPiano3.stop(); } catch(e){} sPiano3.disconnect(); } let vol = isMuted ? 0 : vRange.value/100; gainNode.gain.setTargetAtTime(vol, audioCtx.currentTime, 0.02); if (type === 'noise') { const bSize = audioCtx.sampleRate * 2, buffer = audioCtx.createBuffer(1, bSize, audioCtx.sampleRate), out = buffer.getChannelData(0); for (let i = 0; i < bSize; i++) out[i] = Math.random() * 2 - 1; source = audioCtx.createBufferSource(); source.buffer = buffer; source.loop = true; source.connect(ana); source.start(); } else if (type === 'piano') { source = audioCtx.createOscillator(); sPiano2 = audioCtx.createOscillator(); sPiano3 = audioCtx.createOscillator(); source.type = 'sine'; sPiano2.type = 'triangle'; sPiano3.type = 'sine'; source.frequency.value = freq; sPiano2.frequency.value = freq * 2.01; // Légèrement désaccordé pour le réalisme sPiano3.frequency.value = freq * 3; const gP = audioCtx.createGain(); gP.gain.setValueAtTime(vol, audioCtx.currentTime); // Temps du piano allongé à 3.5 secondes gP.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 3.5); source.connect(gP); sPiano2.connect(gP); sPiano3.connect(gP); gP.connect(ana); source.start(); sPiano2.start(); sPiano3.start(); } else { source = audioCtx.createOscillator(); source.type = (type === 'diapason') ? 'sine' : type; source.frequency.value = (type === 'diapason') ? 440 : freq; source.connect(ana); source.start(); } } function updateMode(mode) { currentType = mode; document.querySelectorAll('.mode-btn').forEach(b => b.style.background = '#444'); document.getElementById('btn' + mode.charAt(0).toUpperCase() + mode.slice(1)).style.background = '#3498db'; document.getElementById('pianoInterface').style.display = (mode === 'piano') ? 'block' : 'none'; document.getElementById('freqContainer').style.display = (mode === 'diapason' || mode === 'noise' || mode === 'piano') ? 'none' : 'block'; if (isPlaying) playSound(mode === 'piano' ? currentPianoFreq : fRange.value, mode); } document.querySelectorAll('.mode-btn').forEach(btn => btn.onclick = () => updateMode(btn.id.replace('btn','').toLowerCase())); document.querySelectorAll('.key').forEach(key => { key.onclick = () => { document.querySelectorAll('.key').forEach(k => k.classList.remove('active')); key.classList.add('active'); currentPianoFreq = parseFloat(key.dataset.f); document.getElementById('pianoNote').textContent = key.dataset.n; if(isPlaying) playSound(currentPianoFreq, 'piano'); }; }); document.getElementById('btnPlay').onclick = function() { init(); if (!isPlaying) { audioCtx.resume(); isPlaying = true; this.textContent = "PAUSE"; this.style.background = "#e67e22"; updateMode(currentType); draw(); } else { audioCtx.suspend(); isPlaying = false; this.textContent = "DÉMARRER"; this.style.background = "#27ae60"; } }; fRange.oninput = () => { fLabel.textContent = fRange.value; if (isPlaying && source && !sPiano2 && currentType !== 'diapason') { source.frequency.setTargetAtTime(fRange.value, audioCtx.currentTime, 0.05); } }; vRange.oninput = () => { vLabel.textContent = vRange.value; if(gainNode && !isMuted) gainNode.gain.setTargetAtTime(vRange.value/100, audioCtx.currentTime, 0.02); }; document.getElementById('btnMute').onclick = function() { isMuted = !isMuted; this.textContent = isMuted ? "ACTIVER SON" : "COUPER SON"; this.style.background = isMuted ? "#c0392b" : "#7f8c8d"; if(gainNode) gainNode.gain.setTargetAtTime(isMuted ? 0 : vRange.value/100, audioCtx.currentTime, 0.05); }; function draw() { requestAnimationFrame(draw); if(isPlaying) { ana.getByteTimeDomainData(dataWave); offset = (offset + 2) % 200; // Vitesse de défilement vers la droite } // Oscilloscope ctxW.fillStyle = "#000"; ctxW.fillRect(0, 0, cW.width, cW.height); ctxW.beginPath(); ctxW.strokeStyle = "#3498db"; ctxW.lineWidth = 2; let slice = cW.width / dataWave.length; for (let i = 0; i < dataWave.length; i++) { // L'offset crée le mouvement vers la droite let index = (i + Math.floor(offset)) % dataWave.length; let x = i * slice; let y = (dataWave[index] / 128.0) * cW.height / 2; if (i === 0) ctxW.moveTo(x, y); else ctxW.lineTo(x, y); } ctxW.stroke(); // Spectre ctxF.fillStyle = "#000"; ctxF.fillRect(0, 0, cF.width, cF.height); ctxF.fillStyle = "#f1c40f"; let freq = (currentType === 'diapason') ? 440 : (currentType === 'piano' ? currentPianoFreq : fRange.value); let p = (freq / 1500) * cF.width; if (currentType === 'sine' || currentType === 'diapason') { ctxF.fillRect(p, 10, 4, cF.height - 20); } else if (currentType === 'noise') { ctxF.fillRect(0, 70, cF.width, 10); } else if (currentType === 'piano') { for(let i=1; i<=4; i++) ctxF.fillRect(p*i, 10+(i*10), 3, cF.height-20-(i*10)); } else { ctxF.fillRect(p, 10, 3, cF.height - 20); ctxF.globalAlpha = 0.5; ctxF.fillRect(p*3, 40, 2, cF.height - 50); ctxF.fillRect(p*5, 60, 2, cF.height - 70); ctxF.globalAlpha = 1.0; } } draw(); </script> </div>