Spaces:
Running
Running
Develop an interactive room acoustics calculator webapp for analyzing room modes in audio spaces. Support both rectangular and L-shaped room configurations with dual input methods: direct numerical entry (length, width, height) and graphical drawing board for wall sketching. Enable interactive subwoofer placement within room layouts. Calculate axial, tangential, and oblique room modes using the formula f = (c/2) × (n/L) where c ≈ 343 m/s. Implement real-time visualization of room geometry, frequency response, mode distribution, and pressure mapping. Include responsive UI with live updates, supporting critical frequencies 20-200 Hz. Target audiophiles, studio planners, and acousticians seeking optimal subwoofer positioning and resonance identification for improved acoustic quality. implement a 3D Visualization with the possibility to change the positioning of the subwoofer via drag and drop
acb1e49 verified | // Room Acoustics Calculator Logic | |
| class RoomAcousticsCalculator { | |
| constructor() { | |
| this.speedOfSound = 343; // m/s | |
| this.roomDimensions = { length: 5, width: 4, height: 3 }; | |
| this.subwooferPosition = { x: 2.5, y: 2, z: 1.5 }; | |
| this.modes = []; | |
| this.init(); | |
| } | |
| init() { | |
| this.calculateModes(); | |
| } | |
| setRoomDimensions(dimensions) { | |
| this.roomDimensions = dimensions; | |
| this.calculateModes(); | |
| } | |
| setSubwooferPosition(position) { | |
| this.subwooferPosition = position; | |
| this.calculatePressureDistribution(); | |
| } | |
| calculateModes(maxFrequency = 200) { | |
| const { length, width, height } = this.roomDimensions; | |
| this.modes = []; | |
| // Maximum mode numbers based on max frequency | |
| const maxN = Math.ceil((2 * maxFrequency * length) / this.speedOfSound); | |
| const maxM = Math.ceil((2 * maxFrequency * width) / this.speedOfSound); | |
| const maxP = Math.ceil((2 * maxFrequency * height) / this.speedOfSound); | |
| for (let n = 0; n <= maxN; n++) { | |
| for (let m = 0; m <= maxM; m++) { | |
| for (let p = 0; p <= maxP; p++) { | |
| if (n === 0 && m === 0 && p === 0) continue; | |
| const frequency = (this.speedOfSound / 2) * | |
| Math.sqrt( | |
| Math.pow(n / length, 2) + | |
| Math.pow(m / width, 2) + | |
| Math.pow(p / height, 2) | |
| ); | |
| if (frequency <= maxFrequency) { | |
| let modeType = 'oblique'; | |
| if ((n > 0 && m === 0 && p === 0) || | |
| (n === 0 && m > 0 && p === 0) || | |
| (n === 0 && m === 0 && p > 0)) { | |
| modeType = 'axial'; | |
| } else if ((n > 0 && m > 0 && p === 0) || | |
| (n > 0 && m === 0 && p > 0) || | |
| (n === 0 && m > 0 && p > 0)) { | |
| modeType = 'tangential'; | |
| } | |
| this.modes.push({ | |
| n, m, p, | |
| frequency: parseFloat(frequency.toFixed(2)), | |
| type: modeType | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| // Sort by frequency | |
| this.modes.sort((a, b) => a.frequency - b.frequency); | |
| return this.modes; | |
| } | |
| calculatePressureAtPosition(x, y, z) { | |
| const { length, width, height } = this.roomDimensions; | |
| let pressure = 0; | |
| // Simplified pressure calculation based on mode shapes | |
| this.modes.forEach(mode => { | |
| const { n, m, p, frequency } = mode; | |
| if (frequency >= 20 && frequency <= 200) { | |
| // Mode shape function | |
| const modeShape = Math.cos((n * Math.PI * x) / length) * | |
| Math.cos((m * Math.PI * y) / width) * | |
| Math.cos((p * Math.PI * z) / height); | |
| // Pressure contribution (simplified) | |
| pressure += Math.abs(modeShape) / (frequency * frequency); | |
| } | |
| }); | |
| return pressure; | |
| } | |
| calculatePressureDistribution() { | |
| // This would normally generate a full 3D pressure map | |
| // For now we'll just return pressure at subwoofer position | |
| return this.calculatePressureAtPosition( | |
| this.subwooferPosition.x, | |
| this.subwooferPosition.y, | |
| this.subwooferPosition.z | |
| ); | |
| } | |
| getCriticalFrequencies() { | |
| const { length, width, height } = this.roomDimensions; | |
| return [ | |
| { | |
| name: "Length Axial", | |
| frequency: parseFloat(((this.speedOfSound / 2) / length).toFixed(2)), | |
| dimension: "length" | |
| }, | |
| { | |
| name: "Width Axial", | |
| frequency: parseFloat(((this.speedOfSound / 2) / width).toFixed(2)), | |
| dimension: "width" | |
| }, | |
| { | |
| name: "Height Axial", | |
| frequency: parseFloat(((this.speedOfSound / 2) / height).toFixed(2)), | |
| dimension: "height" | |
| } | |
| ]; | |
| } | |
| } | |
| // Global calculator instance | |
| const calculator = new RoomAcousticsCalculator(); | |
| // Utility functions | |
| function updateResults() { | |
| document.dispatchEvent(new CustomEvent('calculatorUpdated', { | |
| detail: { | |
| modes: calculator.modes, | |
| criticalFrequencies: calculator.getCriticalFrequencies(), | |
| pressure: calculator.calculatePressureDistribution() | |
| } | |
| })); | |
| } | |
| // Initialize when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| updateResults(); | |
| }); | |
| // Export for use in components | |
| window.calculator = calculator; | |
| window.updateResults = updateResults; |