index.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <!DOCTYPE html>
  2. <html lang="ru">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Механические волны и Фурье (Точно по конспекту)</title>
  7. <style>
  8. body {
  9. font-family: Arial, sans-serif;
  10. background: #fdfaf6; /* Светлый/бежевый фон */
  11. margin: 0;
  12. padding: 10px;
  13. color: #333;
  14. }
  15. .grid-container {
  16. display: grid;
  17. grid-template-columns: 1fr 1fr;
  18. grid-template-rows: auto auto;
  19. gap: 15px;
  20. height: 90vh;
  21. }
  22. .panel {
  23. background: white;
  24. border: 1px solid #ccc;
  25. padding: 15px;
  26. border-radius: 5px;
  27. box-shadow: 0 4px 6px rgba(0,0,0,0.05);
  28. display: flex;
  29. flex-direction: column;
  30. }
  31. canvas {
  32. width: 100%;
  33. height: 200px;
  34. background: #fafafa;
  35. border: 1px solid #ddd;
  36. margin-top: 10px;
  37. }
  38. .formulas {
  39. font-family: 'Courier New', Courier, monospace;
  40. background: #eef7e8;
  41. padding: 10px;
  42. text-align: center;
  43. font-weight: bold;
  44. border: 1px dashed #4CAF50;
  45. margin-bottom: 10px;
  46. }
  47. .tabs {
  48. display: flex;
  49. gap: 5px;
  50. margin-bottom: 15px;
  51. }
  52. .tab {
  53. padding: 8px 15px;
  54. cursor: pointer;
  55. background: #eee;
  56. border: 1px solid #ccc;
  57. border-radius: 4px;
  58. }
  59. .tab.active {
  60. background: #4CAF50;
  61. color: white;
  62. font-weight: bold;
  63. border-color: #4CAF50;
  64. }
  65. .control-row {
  66. margin-bottom: 15px;
  67. display: flex;
  68. align-items: center;
  69. }
  70. label {
  71. display: inline-block;
  72. width: 140px;
  73. font-weight: bold;
  74. }
  75. input[type=range] {
  76. flex-grow: 1;
  77. margin-right: 15px;
  78. }
  79. .value-display {
  80. width: 60px;
  81. text-align: right;
  82. }
  83. .btn {
  84. padding: 10px 15px;
  85. border: none;
  86. border-radius: 4px;
  87. cursor: pointer;
  88. font-weight: bold;
  89. color: white;
  90. margin-right: 10px;
  91. }
  92. .btn-audio { background: #2196F3; }
  93. .btn-dist { background: #9C27B0; }
  94. .btn-audio.active { background: #f44336; }
  95. .btn-dist.active { background: #d32f2f; }
  96. </style>
  97. </head>
  98. <body>
  99. <div class="grid-container">
  100. <!-- 1. Левая верхняя часть -->
  101. <div class="panel">
  102. <h3 style="margin:0;">1. Базовое колебание (выбранное)</h3>
  103. <canvas id="canvas-single"></canvas>
  104. </div>
  105. <!-- 3. Правая верхняя часть -->
  106. <div class="panel">
  107. <h3 style="margin:0;">3. Теорема Фурье (Сумма колебаний)</h3>
  108. <p style="font-size: 0.85em; font-style: italic; color: #666; margin: 5px 0 0 0;">
  109. Любое колебание может быть представлено в виде суммы гармонических колебаний.
  110. </p>
  111. <canvas id="canvas-sum"></canvas>
  112. </div>
  113. <!-- 2. Левая нижняя часть -->
  114. <div class="panel">
  115. <h3 style="margin:0 0 10px 0;">2. Элементы управления</h3>
  116. <!-- Формулы из тетради -->
  117. <div class="formulas">
  118. y = A · cos(wt + θ) &nbsp;&nbsp;|&nbsp;&nbsp; V = 1/T &nbsp;&nbsp;|&nbsp;&nbsp; w = 2πV
  119. </div>
  120. <!-- Вкладки (несколько колебаний) -->
  121. <div class="tabs">
  122. <div class="tab active" onclick="switchTab(0)">V1 (Волна 1)</div>
  123. <div class="tab" onclick="switchTab(1)">V2 (Волна 2)</div>
  124. <div class="tab" onclick="switchTab(2)">V3 (Волна 3)</div>
  125. </div>
  126. <div class="control-row">
  127. <label>Шаг частоты:</label>
  128. <input type="radio" name="step" value="10" onchange="updateStep(10)"> 10 Гц
  129. <input type="radio" name="step" value="100" onchange="updateStep(100)" checked> 100 Гц
  130. </div>
  131. <div class="control-row">
  132. <label>Частота V (до 20 кГц):</label>
  133. <input type="range" id="freq" min="0" max="20000" step="100" value="440" oninput="updateWave()">
  134. <div class="value-display"><span id="valV">440</span> Гц</div>
  135. </div>
  136. <div class="control-row">
  137. <label>Амплитуда A:</label>
  138. <input type="range" id="amp" min="0" max="100" value="80" oninput="updateWave()">
  139. <div class="value-display"><span id="valA">80</span>%</div>
  140. </div>
  141. <div style="margin-top: 10px;">
  142. <button class="btn btn-audio" onclick="toggleAudio()" id="btnAudio">🔊 Вывод на динамике</button>
  143. <button class="btn btn-dist" onclick="toggleClip()" id="btnClip">Добавить нелинейное преобразование</button>
  144. </div>
  145. </div>
  146. <!-- 4. Правая нижняя часть -->
  147. <div class="panel">
  148. <h3 style="margin:0;">4. Спектр</h3>
  149. <canvas id="canvas-spectrum"></canvas>
  150. </div>
  151. </div>
  152. <script>
  153. // Состояние волн (свои настройки для каждой)
  154. let waves = [
  155. { f: 440, a: 80 },
  156. { f: 880, a: 0 },
  157. { f: 1320, a: 0 }
  158. ];
  159. let currentWave = 0;
  160. let audioCtx = null;
  161. let oscillators = [];
  162. let isAudioOn = false;
  163. let isClipped = false;
  164. let freqStep = 100;
  165. let time = 0;
  166. // Переключение вкладок
  167. function switchTab(index) {
  168. currentWave = index;
  169. document.querySelectorAll('.tab').forEach((el, i) => {
  170. el.classList.toggle('active', i === index);
  171. });
  172. document.getElementById('freq').value = waves[index].f;
  173. document.getElementById('amp').value = waves[index].a;
  174. updateLabels();
  175. }
  176. function updateStep(step) {
  177. freqStep = step;
  178. document.getElementById('freq').step = step;
  179. }
  180. function updateLabels() {
  181. document.getElementById('valV').innerText = waves[currentWave].f;
  182. document.getElementById('valA').innerText = waves[currentWave].a;
  183. }
  184. // Обновление параметров волны
  185. function updateWave() {
  186. waves[currentWave].f = parseInt(document.getElementById('freq').value);
  187. waves[currentWave].a = parseInt(document.getElementById('amp').value);
  188. updateLabels();
  189. updateAudio();
  190. }
  191. // Включение/выключение искажения (клиппинга)
  192. function toggleClip() {
  193. isClipped = !isClipped;
  194. let btn = document.getElementById('btnClip');
  195. if (isClipped) {
  196. btn.classList.add('active');
  197. btn.innerText = "Убрать нелинейное преобразование";
  198. } else {
  199. btn.classList.remove('active');
  200. btn.innerText = "Добавить нелинейное преобразование";
  201. }
  202. updateAudio(); // Применяем искажение к звуку
  203. }
  204. // WEB AUDIO API
  205. function toggleAudio() {
  206. if (!audioCtx) {
  207. audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  208. for (let i = 0; i < 3; i++) {
  209. let osc = audioCtx.createOscillator();
  210. let gain = audioCtx.createGain();
  211. osc.type = 'sine';
  212. osc.connect(gain);
  213. gain.connect(audioCtx.destination);
  214. osc.start();
  215. oscillators.push({ osc: osc, gain: gain });
  216. }
  217. }
  218. isAudioOn = !isAudioOn;
  219. let btn = document.getElementById('btnAudio');
  220. if (isAudioOn) {
  221. audioCtx.resume();
  222. btn.classList.add('active');
  223. btn.innerText = "🔇 Выключить звук";
  224. } else {
  225. audioCtx.suspend();
  226. btn.classList.remove('active');
  227. btn.innerText = "🔊 Вывод на динамике";
  228. }
  229. updateAudio();
  230. }
  231. function updateAudio() {
  232. if (!audioCtx) return;
  233. for (let i = 0; i < 3; i++) {
  234. oscillators[i].osc.frequency.setValueAtTime(waves[i].f, audioCtx.currentTime);
  235. // Громкость (0.01 чтобы не было слишком громко)
  236. let vol = isAudioOn ? (waves[i].a * 0.005) : 0;
  237. // Имитация звукового искажения при клиппинге
  238. if (isClipped && isAudioOn && waves[i].a > 0) vol *= 2.5;
  239. oscillators[i].gain.gain.setValueAtTime(vol, audioCtx.currentTime);
  240. // Меняем тип волны для эффекта искажения
  241. oscillators[i].osc.type = isClipped ? 'square' : 'sine';
  242. }
  243. }
  244. // ОТРИСОВКА ГРАФИКОВ
  245. function draw() {
  246. time += 0.08;
  247. // 1. График одного колебания (Левый верхний)
  248. let c1 = document.getElementById('canvas-single');
  249. let ctx1 = c1.getContext('2d');
  250. ctx1.clearRect(0, 0, c1.width, c1.height);
  251. // Ось X
  252. ctx1.beginPath(); ctx1.strokeStyle = "#ccc";
  253. ctx1.moveTo(0, c1.height/2); ctx1.lineTo(c1.width, c1.height/2); ctx1.stroke();
  254. ctx1.beginPath();
  255. ctx1.strokeStyle = "#2196F3";
  256. ctx1.lineWidth = 2;
  257. for (let x = 0; x < c1.width; x++) {
  258. // Масштабируем частоту визуально
  259. let visualF = (waves[currentWave].f / 200) || 1;
  260. let y = Math.cos(x * 0.02 * visualF - time) * (waves[currentWave].a / 100) * (c1.height/2.5);
  261. if(x===0) ctx1.moveTo(x, c1.height/2 - y);
  262. else ctx1.lineTo(x, c1.height/2 - y);
  263. }
  264. ctx1.stroke();
  265. // 2. График суммы Фурье (Правый верхний)
  266. let c2 = document.getElementById('canvas-sum');
  267. let ctx2 = c2.getContext('2d');
  268. ctx2.clearRect(0, 0, c2.width, c2.height);
  269. ctx2.beginPath(); ctx2.strokeStyle = "#ccc";
  270. ctx2.moveTo(0, c2.height/2); ctx2.lineTo(c2.width, c2.height/2); ctx2.stroke();
  271. ctx2.beginPath();
  272. ctx2.strokeStyle = "#E91E63";
  273. ctx2.lineWidth = 2;
  274. for (let x = 0; x < c2.width; x++) {
  275. let sumY = 0;
  276. for (let i = 0; i < 3; i++) {
  277. let visualF = (waves[i].f / 200) || 1;
  278. sumY += Math.cos(x * 0.02 * visualF - time) * (waves[i].a / 100) * (c2.height/2.5);
  279. }
  280. // Применение нелинейного преобразования (Клиппинг) к графику
  281. if (isClipped) {
  282. let limit = (c2.height/4);
  283. if (sumY > limit) sumY = limit;
  284. if (sumY < -limit) sumY = -limit;
  285. }
  286. if(x===0) ctx2.moveTo(x, c2.height/2 - sumY/1.5);
  287. else ctx2.lineTo(x, c2.height/2 - sumY/1.5);
  288. }
  289. ctx2.stroke();
  290. // 3. Спектр (Правый нижний)
  291. let c3 = document.getElementById('canvas-spectrum');
  292. let ctx3 = c3.getContext('2d');
  293. ctx3.clearRect(0, 0, c3.width, c3.height);
  294. // Ось X для спектра
  295. ctx3.beginPath(); ctx3.strokeStyle = "#ccc";
  296. ctx3.moveTo(0, c3.height - 15); ctx3.lineTo(c3.width, c3.height - 15); ctx3.stroke();
  297. ctx3.fillStyle = "#4CAF50";
  298. for (let i = 0; i < 3; i++) {
  299. if (waves[i].a > 0) {
  300. let xPos = (waves[i].f / 20000) * c3.width;
  301. let h = (waves[i].a / 100) * (c3.height - 30);
  302. ctx3.fillRect(Math.max(xPos, 5), c3.height - 15 - h, 10, h);
  303. ctx3.fillStyle = "#333";
  304. ctx3.font = "10px Arial";
  305. ctx3.fillText(waves[i].f, Math.max(xPos-5, 0), c3.height - 5);
  306. ctx3.fillStyle = "#4CAF50";
  307. }
  308. }
  309. // Отрисовка паразитных гармоник при искажении (клиппинге)
  310. if (isClipped && waves[0].a > 0) {
  311. ctx3.fillStyle = "rgba(156, 39, 176, 0.6)";
  312. for (let i = 3; i <= 9; i += 2) {
  313. let fakeF = waves[0].f * i;
  314. if (fakeF > 20000) break;
  315. let xPos = (fakeF / 20000) * c3.width;
  316. let h = (waves[0].a / (i*2) / 100) * (c3.height - 30);
  317. ctx3.fillRect(Math.max(xPos, 5), c3.height - 15 - h, 6, h);
  318. }
  319. }
  320. requestAnimationFrame(draw);
  321. }
  322. // Инициализация размеров холста
  323. function resizeCanvases() {
  324. document.querySelectorAll('canvas').forEach(c => {
  325. c.width = c.parentElement.clientWidth - 30;
  326. });
  327. }
  328. window.addEventListener('resize', resizeCanvases);
  329. resizeCanvases();
  330. draw();
  331. updateLabels();
  332. </script>
  333. </body>
  334. </html>