| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- <!DOCTYPE html>
- <html lang="ru">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Механические волны и Фурье (Точно по конспекту)</title>
- <style>
- body {
- font-family: Arial, sans-serif;
- background: #fdfaf6; /* Светлый/бежевый фон */
- margin: 0;
- padding: 10px;
- color: #333;
- }
- .grid-container {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-rows: auto auto;
- gap: 15px;
- height: 90vh;
- }
- .panel {
- background: white;
- border: 1px solid #ccc;
- padding: 15px;
- border-radius: 5px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.05);
- display: flex;
- flex-direction: column;
- }
- canvas {
- width: 100%;
- height: 200px;
- background: #fafafa;
- border: 1px solid #ddd;
- margin-top: 10px;
- }
- .formulas {
- font-family: 'Courier New', Courier, monospace;
- background: #eef7e8;
- padding: 10px;
- text-align: center;
- font-weight: bold;
- border: 1px dashed #4CAF50;
- margin-bottom: 10px;
- }
- .tabs {
- display: flex;
- gap: 5px;
- margin-bottom: 15px;
- }
- .tab {
- padding: 8px 15px;
- cursor: pointer;
- background: #eee;
- border: 1px solid #ccc;
- border-radius: 4px;
- }
- .tab.active {
- background: #4CAF50;
- color: white;
- font-weight: bold;
- border-color: #4CAF50;
- }
- .control-row {
- margin-bottom: 15px;
- display: flex;
- align-items: center;
- }
- label {
- display: inline-block;
- width: 140px;
- font-weight: bold;
- }
- input[type=range] {
- flex-grow: 1;
- margin-right: 15px;
- }
- .value-display {
- width: 60px;
- text-align: right;
- }
- .btn {
- padding: 10px 15px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: bold;
- color: white;
- margin-right: 10px;
- }
- .btn-audio { background: #2196F3; }
- .btn-dist { background: #9C27B0; }
- .btn-audio.active { background: #f44336; }
- .btn-dist.active { background: #d32f2f; }
- </style>
- </head>
- <body>
- <div class="grid-container">
-
- <!-- 1. Левая верхняя часть -->
- <div class="panel">
- <h3 style="margin:0;">1. Базовое колебание (выбранное)</h3>
- <canvas id="canvas-single"></canvas>
- </div>
- <!-- 3. Правая верхняя часть -->
- <div class="panel">
- <h3 style="margin:0;">3. Теорема Фурье (Сумма колебаний)</h3>
- <p style="font-size: 0.85em; font-style: italic; color: #666; margin: 5px 0 0 0;">
- Любое колебание может быть представлено в виде суммы гармонических колебаний.
- </p>
- <canvas id="canvas-sum"></canvas>
- </div>
- <!-- 2. Левая нижняя часть -->
- <div class="panel">
- <h3 style="margin:0 0 10px 0;">2. Элементы управления</h3>
-
- <!-- Формулы из тетради -->
- <div class="formulas">
- y = A · cos(wt + θ) | V = 1/T | w = 2πV
- </div>
- <!-- Вкладки (несколько колебаний) -->
- <div class="tabs">
- <div class="tab active" onclick="switchTab(0)">V1 (Волна 1)</div>
- <div class="tab" onclick="switchTab(1)">V2 (Волна 2)</div>
- <div class="tab" onclick="switchTab(2)">V3 (Волна 3)</div>
- </div>
- <div class="control-row">
- <label>Шаг частоты:</label>
- <input type="radio" name="step" value="10" onchange="updateStep(10)"> 10 Гц
- <input type="radio" name="step" value="100" onchange="updateStep(100)" checked> 100 Гц
- </div>
-
- <div class="control-row">
- <label>Частота V (до 20 кГц):</label>
- <input type="range" id="freq" min="0" max="20000" step="100" value="440" oninput="updateWave()">
- <div class="value-display"><span id="valV">440</span> Гц</div>
- </div>
-
- <div class="control-row">
- <label>Амплитуда A:</label>
- <input type="range" id="amp" min="0" max="100" value="80" oninput="updateWave()">
- <div class="value-display"><span id="valA">80</span>%</div>
- </div>
- <div style="margin-top: 10px;">
- <button class="btn btn-audio" onclick="toggleAudio()" id="btnAudio">🔊 Вывод на динамике</button>
- <button class="btn btn-dist" onclick="toggleClip()" id="btnClip">Добавить нелинейное преобразование</button>
- </div>
- </div>
- <!-- 4. Правая нижняя часть -->
- <div class="panel">
- <h3 style="margin:0;">4. Спектр</h3>
- <canvas id="canvas-spectrum"></canvas>
- </div>
- </div>
- <script>
- // Состояние волн (свои настройки для каждой)
- let waves = [
- { f: 440, a: 80 },
- { f: 880, a: 0 },
- { f: 1320, a: 0 }
- ];
-
- let currentWave = 0;
- let audioCtx = null;
- let oscillators = [];
- let isAudioOn = false;
- let isClipped = false;
- let freqStep = 100;
- let time = 0;
- // Переключение вкладок
- function switchTab(index) {
- currentWave = index;
- document.querySelectorAll('.tab').forEach((el, i) => {
- el.classList.toggle('active', i === index);
- });
- document.getElementById('freq').value = waves[index].f;
- document.getElementById('amp').value = waves[index].a;
- updateLabels();
- }
- function updateStep(step) {
- freqStep = step;
- document.getElementById('freq').step = step;
- }
- function updateLabels() {
- document.getElementById('valV').innerText = waves[currentWave].f;
- document.getElementById('valA').innerText = waves[currentWave].a;
- }
- // Обновление параметров волны
- function updateWave() {
- waves[currentWave].f = parseInt(document.getElementById('freq').value);
- waves[currentWave].a = parseInt(document.getElementById('amp').value);
- updateLabels();
- updateAudio();
- }
- // Включение/выключение искажения (клиппинга)
- function toggleClip() {
- isClipped = !isClipped;
- let btn = document.getElementById('btnClip');
- if (isClipped) {
- btn.classList.add('active');
- btn.innerText = "Убрать нелинейное преобразование";
- } else {
- btn.classList.remove('active');
- btn.innerText = "Добавить нелинейное преобразование";
- }
- updateAudio(); // Применяем искажение к звуку
- }
- // WEB AUDIO API
- function toggleAudio() {
- if (!audioCtx) {
- audioCtx = new (window.AudioContext || window.webkitAudioContext)();
- for (let i = 0; i < 3; i++) {
- let osc = audioCtx.createOscillator();
- let gain = audioCtx.createGain();
- osc.type = 'sine';
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- osc.start();
- oscillators.push({ osc: osc, gain: gain });
- }
- }
-
- isAudioOn = !isAudioOn;
- let btn = document.getElementById('btnAudio');
- if (isAudioOn) {
- audioCtx.resume();
- btn.classList.add('active');
- btn.innerText = "🔇 Выключить звук";
- } else {
- audioCtx.suspend();
- btn.classList.remove('active');
- btn.innerText = "🔊 Вывод на динамике";
- }
- updateAudio();
- }
- function updateAudio() {
- if (!audioCtx) return;
- for (let i = 0; i < 3; i++) {
- oscillators[i].osc.frequency.setValueAtTime(waves[i].f, audioCtx.currentTime);
- // Громкость (0.01 чтобы не было слишком громко)
- let vol = isAudioOn ? (waves[i].a * 0.005) : 0;
- // Имитация звукового искажения при клиппинге
- if (isClipped && isAudioOn && waves[i].a > 0) vol *= 2.5;
- oscillators[i].gain.gain.setValueAtTime(vol, audioCtx.currentTime);
-
- // Меняем тип волны для эффекта искажения
- oscillators[i].osc.type = isClipped ? 'square' : 'sine';
- }
- }
- // ОТРИСОВКА ГРАФИКОВ
- function draw() {
- time += 0.08;
-
- // 1. График одного колебания (Левый верхний)
- let c1 = document.getElementById('canvas-single');
- let ctx1 = c1.getContext('2d');
- ctx1.clearRect(0, 0, c1.width, c1.height);
-
- // Ось X
- ctx1.beginPath(); ctx1.strokeStyle = "#ccc";
- ctx1.moveTo(0, c1.height/2); ctx1.lineTo(c1.width, c1.height/2); ctx1.stroke();
- ctx1.beginPath();
- ctx1.strokeStyle = "#2196F3";
- ctx1.lineWidth = 2;
- for (let x = 0; x < c1.width; x++) {
- // Масштабируем частоту визуально
- let visualF = (waves[currentWave].f / 200) || 1;
- let y = Math.cos(x * 0.02 * visualF - time) * (waves[currentWave].a / 100) * (c1.height/2.5);
- if(x===0) ctx1.moveTo(x, c1.height/2 - y);
- else ctx1.lineTo(x, c1.height/2 - y);
- }
- ctx1.stroke();
- // 2. График суммы Фурье (Правый верхний)
- let c2 = document.getElementById('canvas-sum');
- let ctx2 = c2.getContext('2d');
- ctx2.clearRect(0, 0, c2.width, c2.height);
-
- ctx2.beginPath(); ctx2.strokeStyle = "#ccc";
- ctx2.moveTo(0, c2.height/2); ctx2.lineTo(c2.width, c2.height/2); ctx2.stroke();
- ctx2.beginPath();
- ctx2.strokeStyle = "#E91E63";
- ctx2.lineWidth = 2;
- for (let x = 0; x < c2.width; x++) {
- let sumY = 0;
- for (let i = 0; i < 3; i++) {
- let visualF = (waves[i].f / 200) || 1;
- sumY += Math.cos(x * 0.02 * visualF - time) * (waves[i].a / 100) * (c2.height/2.5);
- }
-
- // Применение нелинейного преобразования (Клиппинг) к графику
- if (isClipped) {
- let limit = (c2.height/4);
- if (sumY > limit) sumY = limit;
- if (sumY < -limit) sumY = -limit;
- }
-
- if(x===0) ctx2.moveTo(x, c2.height/2 - sumY/1.5);
- else ctx2.lineTo(x, c2.height/2 - sumY/1.5);
- }
- ctx2.stroke();
- // 3. Спектр (Правый нижний)
- let c3 = document.getElementById('canvas-spectrum');
- let ctx3 = c3.getContext('2d');
- ctx3.clearRect(0, 0, c3.width, c3.height);
-
- // Ось X для спектра
- ctx3.beginPath(); ctx3.strokeStyle = "#ccc";
- ctx3.moveTo(0, c3.height - 15); ctx3.lineTo(c3.width, c3.height - 15); ctx3.stroke();
- ctx3.fillStyle = "#4CAF50";
- for (let i = 0; i < 3; i++) {
- if (waves[i].a > 0) {
- let xPos = (waves[i].f / 20000) * c3.width;
- let h = (waves[i].a / 100) * (c3.height - 30);
- ctx3.fillRect(Math.max(xPos, 5), c3.height - 15 - h, 10, h);
-
- ctx3.fillStyle = "#333";
- ctx3.font = "10px Arial";
- ctx3.fillText(waves[i].f, Math.max(xPos-5, 0), c3.height - 5);
- ctx3.fillStyle = "#4CAF50";
- }
- }
- // Отрисовка паразитных гармоник при искажении (клиппинге)
- if (isClipped && waves[0].a > 0) {
- ctx3.fillStyle = "rgba(156, 39, 176, 0.6)";
- for (let i = 3; i <= 9; i += 2) {
- let fakeF = waves[0].f * i;
- if (fakeF > 20000) break;
- let xPos = (fakeF / 20000) * c3.width;
- let h = (waves[0].a / (i*2) / 100) * (c3.height - 30);
- ctx3.fillRect(Math.max(xPos, 5), c3.height - 15 - h, 6, h);
- }
- }
- requestAnimationFrame(draw);
- }
- // Инициализация размеров холста
- function resizeCanvases() {
- document.querySelectorAll('canvas').forEach(c => {
- c.width = c.parentElement.clientWidth - 30;
- });
- }
-
- window.addEventListener('resize', resizeCanvases);
- resizeCanvases();
- draw();
- updateLabels();
- </script>
- </body>
- </html>
|