Changed around line 1
- const f1FrequencyInput = document.getElementById('f1-frequency');
- const f2FrequencyInput = document.getElementById('f2-frequency');
- const amplitudeInput = document.getElementById('amplitude');
- const phaseInput = document.getElementById('phase');
- const generateBtn = document.getElementById('generate-btn');
- const addBtn = document.getElementById('add-btn');
- const multiplyBtn = document.getElementById('multiply-btn');
- const subtractBtn = document.getElementById('subtract-btn');
- const timeDomainCanvas = document.getElementById('time-domain');
- const frequencyDomainCanvas = document.getElementById('frequency-domain');
-
- let f1Signal = [];
- let f2Signal = [];
- let combinedSignal = [];
-
- function generateSineWave(frequency, amplitude, phase, sampleRate = 44100, duration = 1) {
- const signal = [];
- const numSamples = sampleRate * duration;
- for (let i = 0; i < numSamples; i++) {
- const t = i / sampleRate;
- signal.push(amplitude * Math.sin(2 * Math.PI * frequency * t + phase));
+ class SignalGenerator {
+ constructor() {
+ this.initializeCanvases();
+ this.setupEventListeners();
+ this.drawInitialSignals();
- return signal;
- }
- function plotTimeDomain(signal, canvas) {
- const ctx = canvas.getContext('2d');
- const width = canvas.width;
- const height = canvas.height;
- ctx.clearRect(0, 0, width, height);
- ctx.beginPath();
- ctx.moveTo(0, height / 2);
-
- const step = width / signal.length;
- for (let i = 0; i < signal.length; i++) {
- const x = i * step;
- const y = height / 2 - signal[i] * (height / 4);
- ctx.lineTo(x, y);
+ initializeCanvases() {
+ this.timeCanvas = document.getElementById('time-domain');
+ this.freqCanvas = document.getElementById('frequency-domain');
+ this.timeCtx = this.timeCanvas.getContext('2d');
+ this.freqCtx = this.freqCanvas.getContext('2d');
+
+ this.resizeCanvases();
+ window.addEventListener('resize', () => this.resizeCanvases());
-
- ctx.strokeStyle = '#4a90e2';
- ctx.lineWidth = 2;
- ctx.stroke();
- }
- function plotFrequencyDomain(signal, canvas) {
- const ctx = canvas.getContext('2d');
- const width = canvas.width;
- const height = canvas.height;
- ctx.clearRect(0, 0, width, height);
-
- const fft = new FFT(signal.length, 44100);
- fft.forward(signal);
-
- const spectrum = fft.spectrum;
- const barWidth = width / spectrum.length;
-
- for (let i = 0; i < spectrum.length; i++) {
- const barHeight = spectrum[i] * height;
- ctx.fillStyle = '#50e3c2';
- ctx.fillRect(i * barWidth, height - barHeight, barWidth, barHeight);
+ resizeCanvases() {
+ const setCanvasSize = (canvas) => {
+ canvas.width = canvas.offsetWidth;
+ canvas.height = canvas.offsetHeight;
+ };
+ setCanvasSize(this.timeCanvas);
+ setCanvasSize(this.freqCanvas);
- }
- function generateSignals() {
- const f1 = parseFloat(f1FrequencyInput.value);
- const f2 = parseFloat(f2FrequencyInput.value);
- const amplitude = parseFloat(amplitudeInput.value);
- const phase = parseFloat(phaseInput.value);
-
- f1Signal = generateSineWave(f1, amplitude, phase);
- f2Signal = generateSineWave(f2, amplitude, phase);
-
- plotTimeDomain(f1Signal, timeDomainCanvas);
- plotFrequencyDomain(f1Signal, frequencyDomainCanvas);
- }
-
- function addSignals() {
- combinedSignal = f1Signal.map((value, index) => value + f2Signal[index]);
- plotTimeDomain(combinedSignal, timeDomainCanvas);
- plotFrequencyDomain(combinedSignal, frequencyDomainCanvas);
- }
+ setupEventListeners() {
+ const controls = ['f1-type', 'f1-frequency', 'f1-amplitude',
+ 'f2-type', 'f2-frequency', 'f2-amplitude'];
+
+ controls.forEach(id => {
+ const element = document.getElementById(id);
+ element.addEventListener('input', () => this.updateSignals());
+
+ if (id.includes('frequency') || id.includes('amplitude')) {
+ element.addEventListener('input', () => {
+ const valueDisplay = document.getElementById(`${id}-value`);
+ const value = element.value;
+ valueDisplay.textContent = id.includes('frequency') ?
+ `${value} Hz` : value;
+ });
+ }
+ });
+
+ document.getElementById('btn-add').addEventListener('click', () => {
+ this.operation = 'add';
+ this.updateSignals();
+ });
+
+ document.getElementById('btn-multiply').addEventListener('click', () => {
+ this.operation = 'multiply';
+ this.updateSignals();
+ });
+
+ document.getElementById('btn-reset').addEventListener('click', () => {
+ this.operation = null;
+ this.updateSignals();
+ });
+ }
- function multiplySignals() {
- combinedSignal = f1Signal.map((value, index) => value * f2Signal[index]);
- plotTimeDomain(combinedSignal, timeDomainCanvas);
- plotFrequencyDomain(combinedSignal, frequencyDomainCanvas);
- }
+ generateSignal(type, frequency, amplitude, t) {
+ switch(type) {
+ case 'sine':
+ return amplitude * Math.sin(2 * Math.PI * frequency * t);
+ case 'square':
+ return amplitude * Math.sign(Math.sin(2 * Math.PI * frequency * t));
+ case 'triangle':
+ return amplitude * (Math.asin(Math.sin(2 * Math.PI * frequency * t)) * 2 / Math.PI);
+ case 'sawtooth':
+ return amplitude * (2 * (frequency * t - Math.floor(0.5 + frequency * t)));
+ default:
+ return 0;
+ }
+ }
- function subtractSignals() {
- combinedSignal = f1Signal.map((value, index) => value - f2Signal[index]);
- plotTimeDomain(combinedSignal, timeDomainCanvas);
- plotFrequencyDomain(combinedSignal, frequencyDomainCanvas);
- }
+ drawSignal(ctx, signal, color) {
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
- generateBtn.addEventListener('click', generateSignals);
- addBtn.addEventListener('click', addSignals);
- multiplyBtn.addEventListener('click', multiplySignals);
- subtractBtn.addEventListener('click', subtractSignals);
-
- // FFT implementation
- class FFT {
- constructor(bufferSize, sampleRate) {
- this.bufferSize = bufferSize;
- this.sampleRate = sampleRate;
- this.bandwidth = sampleRate / bufferSize;
- this.spectrum = new Float32Array(bufferSize / 2);
- this.real = new Float32Array(bufferSize);
- this.imag = new Float32Array(bufferSize);
- }
+ for (let x = 0; x < ctx.canvas.width; x++) {
+ const t = x / ctx.canvas.width;
+ const y = (signal(t) + 1) * ctx.canvas.height / 2;
+ x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
+ }
- forward(buffer) {
- this.real.set(buffer);
- this.imag.fill(0);
- this.transform(this.real, this.imag);
- this.calculateSpectrum();
+ ctx.stroke();
- transform(real, imag) {
- const n = real.length;
- if (n <= 1) return;
-
- const evenReal = new Float32Array(n / 2);
- const evenImag = new Float32Array(n / 2);
- const oddReal = new Float32Array(n / 2);
- const oddImag = new Float32Array(n / 2);
+ calculateFFT(signal) {
+ const N = 512;
+ const data = new Array(N);
+
+ for (let i = 0; i < N; i++) {
+ data[i] = signal(i / N);
+ }
- for (let i = 0; i < n / 2; i++) {
- evenReal[i] = real[2 * i];
- evenImag[i] = imag[2 * i];
- oddReal[i] = real[2 * i + 1];
- oddImag[i] = imag[2 * i + 1];
+ // Simple DFT implementation (for demonstration)
+ const spectrum = new Array(N/2);
+ for (let k = 0; k < N/2; k++) {
+ let real = 0, imag = 0;
+ for (let n = 0; n < N; n++) {
+ const angle = 2 * Math.PI * k * n / N;
+ real += data[n] * Math.cos(angle);
+ imag -= data[n] * Math.sin(angle);
+ }
+ spectrum[k] = Math.sqrt(real*real + imag*imag) / N;
+
+ return spectrum;
+ }
- this.transform(evenReal, evenImag);
- this.transform(oddReal, oddImag);
+ drawSpectrum(ctx, spectrum) {
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ const barWidth = ctx.canvas.width / spectrum.length;
+ const maxMagnitude = Math.max(...spectrum);
+
+ ctx.fillStyle = '#3498db';
+ spectrum.forEach((magnitude, i) => {
+ const height = (magnitude / maxMagnitude) * ctx.canvas.height;
+ ctx.fillRect(
+ i * barWidth,
+ ctx.canvas.height - height,
+ barWidth - 1,
+ height
+ );
+ });
+ }
- for (let k = 0; k < n / 2; k++) {
- const tReal = Math.cos(2 * Math.PI * k / n) * oddReal[k] - Math.sin(2 * Math.PI * k / n) * oddImag[k];
- const tImag = Math.sin(2 * Math.PI * k / n) * oddReal[k] + Math.cos(2 * Math.PI * k / n) * oddImag[k];
+ updateSignals() {
+ const f1Type = document.getElementById('f1-type').value;
+ const f1Freq = parseFloat(document.getElementById('f1-frequency').value);
+ const f1Amp = parseFloat(document.getElementById('f1-amplitude').value);
+
+ const f2Type = document.getElementById('f2-type').value;
+ const f2Freq = parseFloat(document.getElementById('f2-frequency').value);
+ const f2Amp = parseFloat(document.getElementById('f2-amplitude').value);
+
+ const signal1 = (t) => this.generateSignal(f1Type, f1Freq, f1Amp, t);
+ const signal2 = (t) => this.generateSignal(f2Type, f2Freq, f2Amp, t);
+
+ let combinedSignal;
+ if (this.operation === 'add') {
+ combinedSignal = (t) => signal1(t) + signal2(t);
+ } else if (this.operation === 'multiply') {
+ combinedSignal = (t) => signal1(t) * signal2(t);
+ }
- real[k] = evenReal[k] + tReal;
- imag[k] = evenImag[k] + tImag;
- real[k + n / 2] = evenReal[k] - tReal;
- imag[k + n / 2] = evenImag[k] - tImag;
+ // Clear canvases
+ this.timeCtx.clearRect(0, 0, this.timeCanvas.width, this.timeCanvas.height);
+
+ // Draw signals
+ this.drawSignal(this.timeCtx, signal1, '#3498db');
+ this.drawSignal(this.timeCtx, signal2, '#e74c3c');
+ if (combinedSignal) {
+ this.drawSignal(this.timeCtx, combinedSignal, '#2ecc71');
+
+ // Draw spectrum
+ const spectrum = this.calculateFFT(combinedSignal || signal1);
+ this.drawSpectrum(this.freqCtx, spectrum);
- calculateSpectrum() {
- for (let i = 0; i < this.bufferSize / 2; i++) {
- this.spectrum[i] = Math.sqrt(this.real[i] * this.real[i] + this.imag[i] * this.imag[i]);
- }
+ drawInitialSignals() {
+ this.updateSignals();
- }
+ }
+
+ document.addEventListener('DOMContentLoaded', () => {
+ new SignalGenerator();
+ });