在这里插入图片描述

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


🌊 一、声波干涉:数学之美

📚 1.1 波动基础

波是能量和信息传递的基本形式。声波作为机械波,通过介质粒子的振动传播能量。

波的基本方程

一维波动方程:
∂²y/∂t² = v² · ∂²y/∂x²

其中:
- y:位移
- t:时间
- x:位置
- v:波速

简谐波解:
y(x, t) = A·sin(kx - ωt + φ)

参数说明:
- A:振幅
- k = 2π/λ:波数
- ω = 2πf:角频率
- φ:初相位
- λ:波长
- f:频率
- v = λf:波速

波形示意

y(x, 0) = A·sin(kx)

    A  ────●─────────────────
        ╱   ╲
       ╱     ╲
   0 ─●───────●───────●────  平衡位置
       ╲     ╱
        ╲   ╱
   -A ────●─────────────────
        
   0    λ/4  λ/2  3λ/4  λ
   
波长 λ:相邻波峰(或波谷)之间的距离

波的传播

t = 0:     t = T/4:    t = T/2:    t = 3T/4:   t = T:
   ●           ●           ●           ●           ●
  ╱ ╲         ╱ ╲         ╱ ╲         ╱ ╲         ╱ ╲
 ●   ●       ●   ●       ●   ●       ●   ●       ●   ●
  ╲ ╱         ╲ ╱         ╲ ╱         ╲ ╱         ╲ ╱
   ●           ●           ●           ●           ●
    →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
                        波的传播方向

波形向右移动一个波长 λ 需要一个周期 T

📐 1.2 叠加原理

当两列或多列波在空间中相遇时,它们遵循叠加原理:合位移等于各波单独作用时位移的矢量和。

叠加原理

y_total = y₁ + y₂ + y₃ + ...

对于两列简谐波:
y₁ = A₁·sin(k₁x - ω₁t + φ₁)
y₂ = A₂·sin(k₂x - ω₂t + φ₂)

合成波:
y = y₁ + y₂

特殊情况:
1. 同频率、同方向:
   y = A·sin(kx - ωt + φ)
   其中 A 和 φ 取决于相位差

2. 同频率、反方向(驻波):
   y = 2A·cos(kx)·sin(ωt)

3. 频率相近(拍):
   y = 2A·cos(Δω·t/2)·sin(ω_avg·t)

干涉类型

类型 条件 结果
🟢 相长干涉 相位差 = 2nπ 振幅增大
🔴 相消干涉 相位差 = (2n+1)π 振幅减小
🟡 部分干涉 其他相位差 部分叠加

🔬 1.3 驻波形成

当两列振幅、频率相同但方向相反的波相遇时,形成驻波。驻波的特点是某些点始终静止(波节),某些点振动最大(波腹)。

驻波方程

入射波:y₁ = A·sin(kx - ωt)
反射波:y₂ = A·sin(kx + ωt)

叠加:
y = y₁ + y₂ = 2A·sin(kx)·cos(ωt)

驻波特点:
- 波节:sin(kx) = 0 → x = nλ/2
- 波腹:sin(kx) = ±1 → x = (2n+1)λ/4
- 相邻波节距离:λ/2
- 相邻波腹距离:λ/2

驻波示意

不同时刻的波形:

t = 0:     t = T/4:    t = T/2:    t = 3T/4:
  ●           ●                       ●
 ╱ ╲         ╱ ╲         ╱╲          ╱ ╲
●   ●       ●   ●       ●  ●        ●   ●
 ╲ ╱         ╲ ╱         ╲╱          ╲ ╱
  ●           ●           ●           ●
N   A   N   A   N   A   N   A   N
│   │   │   │   │   │   │   │   │
波节(N):始终静止
波腹(A):振动最大

相邻波节距离 = λ/2

驻波的应用

1. 弦乐器:
   - 两端固定的弦形成驻波
   - 基频:f₁ = v/(2L)
   - 泛音:fₙ = n·f₁

2. 管乐器:
   - 开管:两端为波腹
   - 闭管:一端波节,一端波腹

3. 微波炉:
   - 腔体内形成驻波
   - 加热不均匀的原因

4. 量子力学:
   - 电子在原子中的驻波
   - 波函数的驻波解

🎯 1.4 拍现象

当两个频率相近的波叠加时,产生振幅周期性变化的现象,称为拍。

拍的形成

两个频率相近的波:
y₁ = A·sin(ω₁t)
y₂ = A·sin(ω₂t)

叠加:
y = 2A·cos(Δω·t/2)·sin(ω_avg·t)

其中:
- Δω = |ω₁ - ω₂|:角频率差
- ω_avg = (ω₁ + ω₂)/2:平均角频率
- 拍频:f_beat = |f₁ - f₂|

振幅包络:
A(t) = 2A·|cos(Δω·t/2)|

拍现象示意

高频载波被低频包络调制:

    ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
   ╱                      ╲
  ╱                        ╲
 ╱                          ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
╱                            ╲              ╲
                              ╲              ╲
                               ╲              ╲
                                ╲╱╲╱╲╱╲╱╲╱╲╱╲╱

包络频率 = 拍频 = |f₁ - f₂|

🔧 二、波动模拟的 Dart 实现

🧮 2.1 波动方程求解

import 'dart:math';

/// 波动参数
class WaveParameters {
  final double amplitude;
  final double wavelength;
  final double frequency;
  final double phase;
  final double speed;
  
  WaveParameters({
    required this.amplitude,
    required this.wavelength,
    required this.frequency,
    this.phase = 0,
  }) : speed = wavelength * frequency;
  
  double get waveNumber => 2 * pi / wavelength;
  double get angularFrequency => 2 * pi * frequency;
  double get period => 1 / frequency;
  
  double displacement(double x, double t) {
    return amplitude * sin(waveNumber * x - angularFrequency * t + phase);
  }
}

/// 波动模拟器
class WaveSimulator {
  final List<WaveParameters> waves;
  
  WaveSimulator({List<WaveParameters>? waves}) : waves = waves ?? [];
  
  void addWave(WaveParameters wave) {
    waves.add(wave);
  }
  
  double totalDisplacement(double x, double t) {
    return waves.fold(0.0, (sum, wave) => sum + wave.displacement(x, t));
  }
  
  List<double> generateWaveform(double xStart, double xEnd, int points, double t) {
    final dx = (xEnd - xStart) / (points - 1);
    return List.generate(points, (i) {
      final x = xStart + i * dx;
      return totalDisplacement(x, t);
    });
  }
}

/// 驻波模拟器
class StandingWaveSimulator {
  final double amplitude;
  final double wavelength;
  final double frequency;
  
  StandingWaveSimulator({
    required this.amplitude,
    required this.wavelength,
    required this.frequency,
  });
  
  double get waveNumber => 2 * pi / wavelength;
  double get angularFrequency => 2 * pi * frequency;
  
  double displacement(double x, double t) {
    return 2 * amplitude * sin(waveNumber * x) * cos(angularFrequency * t);
  }
  
  List<double> nodePositions(double length) {
    final positions = <double>[];
    final n = (length * 2 / wavelength).floor();
    for (int i = 0; i <= n; i++) {
      positions.add(i * wavelength / 2);
    }
    return positions;
  }
  
  List<double> antinodePositions(double length) {
    final positions = <double>[];
    final n = (length * 2 / wavelength).floor();
    for (int i = 0; i < n; i++) {
      positions.add((i + 0.5) * wavelength / 2);
    }
    return positions;
  }
}

/// 拍现象模拟器
class BeatSimulator {
  final double amplitude;
  final double frequency1;
  final double frequency2;
  
  BeatSimulator({
    required this.amplitude,
    required this.frequency1,
    required this.frequency2,
  });
  
  double get beatFrequency => (frequency1 - frequency2).abs();
  double get averageFrequency => (frequency1 + frequency2) / 2;
  
  double displacement(double t) {
    final omega1 = 2 * pi * frequency1;
    final omega2 = 2 * pi * frequency2;
    return 2 * amplitude * cos((omega1 - omega2) * t / 2) * sin((omega1 + omega2) * t / 2);
  }
  
  double envelope(double t) {
    return 2 * amplitude * cos(pi * beatFrequency * t).abs();
  }
}

⚡ 2.2 二维干涉模拟

/// 二维波动场
class WaveField2D {
  final int width;
  final int height;
  final double dx;
  
  late List<List<double>> _field;
  late List<List<double>> _previousField;
  
  final double waveSpeed;
  final double damping;
  
  WaveField2D({
    required this.width,
    required this.height,
    this.dx = 1.0,
    this.waveSpeed = 1.0,
    this.damping = 0.99,
  }) {
    _field = List.generate(height, (_) => List.filled(width, 0.0));
    _previousField = List.generate(height, (_) => List.filled(width, 0.0));
  }
  
  void addSource(int x, int y, double amplitude) {
    if (x >= 0 && x < width && y >= 0 && y < height) {
      _field[y][x] += amplitude;
    }
  }
  
  void update(double dt) {
    final c2 = waveSpeed * waveSpeed;
    final r = c2 * dt * dt / (dx * dx);
    
    final newField = List.generate(height, (_) => List.filled(width, 0.0));
    
    for (int y = 1; y < height - 1; y++) {
      for (int x = 1; x < width - 1; x++) {
        final laplacian = _field[y][x - 1] + _field[y][x + 1] +
                          _field[y - 1][x] + _field[y + 1][x] -
                          4 * _field[y][x];
        
        newField[y][x] = damping * (2 * _field[y][x] - _previousField[y][x] + r * laplacian);
      }
    }
    
    _previousField = _field;
    _field = newField;
  }
  
  double getValue(int x, int y) {
    if (x >= 0 && x < width && y >= 0 && y < height) {
      return _field[y][x];
    }
    return 0;
  }
  
  void reset() {
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        _field[y][x] = 0;
        _previousField[y][x] = 0;
      }
    }
  }
}

/// 双缝干涉模拟
class DoubleSlitInterference {
  final int gridSize;
  final double slitSeparation;
  final double slitWidth;
  final double wavelength;
  final double screenDistance;
  
  DoubleSlitInterference({
    required this.gridSize,
    required this.slitSeparation,
    required this.slitWidth,
    required this.wavelength,
    required this.screenDistance,
  });
  
  double intensity(double y) {
    final k = 2 * pi / wavelength;
    
    final theta = atan(y / screenDistance);
    
    final alpha = k * slitSeparation * sin(theta) / 2;
    final beta = k * slitWidth * sin(theta) / 2;
    
    final interferencePattern = cos(alpha) * cos(alpha);
    final diffractionEnvelope = sinc(beta) * sinc(beta);
    
    return interferencePattern * diffractionEnvelope;
  }
  
  double sinc(double x) {
    if (x.abs() < 1e-10) return 1.0;
    return sin(x) / x;
  }
  
  List<double> generatePattern(double yMin, double yMax, int points) {
    final dy = (yMax - yMin) / (points - 1);
    return List.generate(points, (i) {
      final y = yMin + i * dy;
      return intensity(y);
    });
  }
}

🎨 2.3 动画控制器

import 'package:flutter/material.dart';

/// 波动动画控制器
class WaveAnimationController extends ChangeNotifier {
  double _time = 0;
  double _timeScale = 1.0;
  bool _isRunning = true;
  
  final WaveSimulator _simulator = WaveSimulator();
  final List<double> _history = [];
  final int _maxHistoryLength = 500;
  
  WaveAnimationController() {
    _simulator.addWave(WaveParameters(
      amplitude: 1.0,
      wavelength: 100.0,
      frequency: 1.0,
      phase: 0,
    ));
  }
  
  double get time => _time;
  double get timeScale => _timeScale;
  bool get isRunning => _isRunning;
  WaveSimulator get simulator => _simulator;
  List<double> get history => _history;
  
  void update(double dt) {
    if (_isRunning) {
      _time += dt * _timeScale;
      notifyListeners();
    }
  }
  
  void toggleRunning() {
    _isRunning = !_isRunning;
    notifyListeners();
  }
  
  void setTimeScale(double scale) {
    _timeScale = scale.clamp(0.1, 5.0);
    notifyListeners();
  }
  
  void addWave(double amplitude, double wavelength, double frequency, double phase) {
    _simulator.addWave(WaveParameters(
      amplitude: amplitude,
      wavelength: wavelength,
      frequency: frequency,
      phase: phase,
    ));
    notifyListeners();
  }
  
  void reset() {
    _time = 0;
    _history.clear();
    notifyListeners();
  }
}

📦 三、完整示例代码

以下是完整的波动干涉可视化示例代码:

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(const WaveApp());
}

class WaveApp extends StatelessWidget {
  const WaveApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '声波干涉',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark),
        useMaterial3: true,
      ),
      home: const WaveHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class WaveHomePage extends StatelessWidget {
  const WaveHomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('🌊 声波干涉'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildCard(context, title: '波的叠加', description: '多波干涉原理', icon: Icons.add_circle, color: Colors.blue,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WaveSuperpositionDemo()))),
          _buildCard(context, title: '驻波形成', description: '波节与波腹', icon: Icons.horizontal_rule, color: Colors.purple,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const StandingWaveDemo()))),
          _buildCard(context, title: '拍现象', description: '频率差产生的节拍', icon: Icons.graphic_eq, color: Colors.orange,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BeatDemo()))),
          _buildCard(context, title: '双缝干涉', description: '杨氏双缝实验', icon: Icons.view_column, color: Colors.teal,
              onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DoubleSlitDemo()))),
        ],
      ),
    );
  }

  Widget _buildCard(BuildContext context, {required String title, required String description, required IconData icon, required Color color, required VoidCallback onTap}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(16),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(children: [
            Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
                child: Icon(icon, color: color, size: 28)),
            const SizedBox(width: 16),
            Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
              Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              const SizedBox(height: 4),
              Text(description, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
            ])),
            Icon(Icons.chevron_right, color: Colors.grey[400]),
          ]),
        ),
      ),
    );
  }
}

/// 波动参数
class Wave {
  double amplitude, wavelength, frequency, phase;
  Wave({required this.amplitude, required this.wavelength, required this.frequency, this.phase = 0});
  
  double get waveNumber => 2 * pi / wavelength;
  double get angularFrequency => 2 * pi * frequency;
  
  double displacement(double x, double t) => amplitude * sin(waveNumber * x - angularFrequency * t + phase);
}

/// 波的叠加演示
class WaveSuperpositionDemo extends StatefulWidget {
  const WaveSuperpositionDemo({super.key});
  
  State<WaveSuperpositionDemo> createState() => _WaveSuperpositionDemoState();
}

class _WaveSuperpositionDemoState extends State<WaveSuperpositionDemo> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  double _time = 0;
  final List<Wave> _waves = [Wave(amplitude: 1, wavelength: 100, frequency: 1), Wave(amplitude: 0.8, wavelength: 120, frequency: 0.8)];
  double _phaseDiff = 0;

  
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _ctrl.addListener(_update);
  }
  
  void _update() { _time += 0.016; setState(() {}); }

  
  void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('波的叠加')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: SuperpositionPainter(_waves, _time), size: Size.infinite)),
        _buildControls(),
      ]),
    );
  }

  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
      child: Column(mainAxisSize: MainAxisSize.min, children: [
        Row(children: [
          const Text('相位差: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _phaseDiff, min: 0, max: 2 * pi, onChanged: (v) => setState(() { _phaseDiff = v; _waves[1].phase = v; }))),
          Text('${(_phaseDiff / pi).toStringAsFixed(2)}π', style: const TextStyle(color: Colors.blue)),
        ]),
        Row(children: [
          const Text('波2振幅: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _waves[1].amplitude, min: 0, max: 2, onChanged: (v) => setState(() => _waves[1].amplitude = v))),
          Text(_waves[1].amplitude.toStringAsFixed(2), style: const TextStyle(color: Colors.blue)),
        ]),
        Text(_getInterferenceType(), style: TextStyle(color: _getInterferenceColor())),
      ]),
    );
  }
  
  String _getInterferenceType() {
    if (_phaseDiff < 0.1 || (_phaseDiff - 2 * pi).abs() < 0.1) return '相长干涉 - 振幅增大';
    if ((_phaseDiff - pi).abs() < 0.1) return '相消干涉 - 振幅减小';
    return '部分干涉';
  }
  
  Color _getInterferenceColor() {
    if (_phaseDiff < 0.1 || (_phaseDiff - 2 * pi).abs() < 0.1) return Colors.green;
    if ((_phaseDiff - pi).abs() < 0.1) return Colors.red;
    return Colors.yellow;
  }
}

class SuperpositionPainter extends CustomPainter {
  final List<Wave> waves;
  final double time;
  SuperpositionPainter(this.waves, this.time);

  
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final colors = [Colors.blue, Colors.orange, Colors.green];
    final centerY = size.height / 2;
    final scale = size.height / 4;
    
    for (int w = 0; w < waves.length; w++) {
      final path = Path();
      for (int i = 0; i <= size.width.toInt(); i++) {
        final x = i.toDouble();
        final y = waves[w].displacement(x, time) * scale;
        if (i == 0) path.moveTo(x, centerY - y);
        else path.lineTo(x, centerY - y);
      }
      canvas.drawPath(path, Paint()..color = colors[w].withOpacity(0.5)..style = PaintingStyle.stroke..strokeWidth = 2);
    }
    
    final sumPath = Path();
    for (int i = 0; i <= size.width.toInt(); i++) {
      final x = i.toDouble();
      double sum = 0;
      for (final wave in waves) { sum += wave.displacement(x, time); }
      final y = sum * scale;
      if (i == 0) sumPath.moveTo(x, centerY - y);
      else sumPath.lineTo(x, centerY - y);
    }
    canvas.drawPath(sumPath, Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 3);
  }

  
  bool shouldRepaint(covariant SuperpositionPainter old) => true;
}

/// 驻波演示
class StandingWaveDemo extends StatefulWidget {
  const StandingWaveDemo({super.key});
  
  State<StandingWaveDemo> createState() => _StandingWaveDemoState();
}

class _StandingWaveDemoState extends State<StandingWaveDemo> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  double _time = 0;
  double _wavelength = 100;
  double _frequency = 1;
  double _amplitude = 1;

  
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _ctrl.addListener(_update);
  }
  
  void _update() { _time += 0.016; setState(() {}); }

  
  void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('驻波形成')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: StandingWavePainter(_wavelength, _frequency, _amplitude, _time), size: Size.infinite)),
        _buildControls(),
      ]),
    );
  }

  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
      child: Column(mainAxisSize: MainAxisSize.min, children: [
        Row(children: [
          const Text('波长: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _wavelength, min: 50, max: 200, onChanged: (v) => setState(() => _wavelength = v))),
          Text(_wavelength.toStringAsFixed(0), style: const TextStyle(color: Colors.purple)),
        ]),
        Row(children: [
          const Text('频率: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _frequency, min: 0.5, max: 3, onChanged: (v) => setState(() => _frequency = v))),
          Text(_frequency.toStringAsFixed(2), style: const TextStyle(color: Colors.purple)),
        ]),
        Text('波节间距: ${(_wavelength / 2).toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70)),
      ]),
    );
  }
}

class StandingWavePainter extends CustomPainter {
  final double wavelength, frequency, amplitude, time;
  StandingWavePainter(this.wavelength, this.frequency, this.amplitude, this.time);

  
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final k = 2 * pi / wavelength;
    final omega = 2 * pi * frequency;
    final centerY = size.height / 2;
    final scale = size.height / 4;
    
    for (int i = 0; i <= size.width.toInt(); i++) {
      final x = i.toDouble();
      final nodeVal = sin(k * x).abs();
      final hue = nodeVal * 60;
      canvas.drawCircle(Offset(x, centerY), 1, Paint()..color = HSVColor.fromAHSV(0.3, hue, 0.8, 0.8).toColor());
    }
    
    final path = Path();
    for (int i = 0; i <= size.width.toInt(); i++) {
      final x = i.toDouble();
      final y = 2 * amplitude * sin(k * x) * cos(omega * time) * scale;
      if (i == 0) path.moveTo(x, centerY - y);
      else path.lineTo(x, centerY - y);
    }
    canvas.drawPath(path, Paint()..color = Colors.purple..style = PaintingStyle.stroke..strokeWidth = 3);
    
    for (double nodeX = 0; nodeX <= size.width; nodeX += wavelength / 2) {
      canvas.drawCircle(Offset(nodeX, centerY), 6, Paint()..color = Colors.red);
    }
    
    for (double antinodeX = wavelength / 4; antinodeX <= size.width; antinodeX += wavelength / 2) {
      canvas.drawCircle(Offset(antinodeX, centerY), 6, Paint()..color = Colors.green);
    }
  }

  
  bool shouldRepaint(covariant StandingWavePainter old) => true;
}

/// 拍现象演示
class BeatDemo extends StatefulWidget {
  const BeatDemo({super.key});
  
  State<BeatDemo> createState() => _BeatDemoState();
}

class _BeatDemoState extends State<BeatDemo> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  double _time = 0;
  double _freq1 = 5.0;
  double _freq2 = 5.5;

  
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
    _ctrl.addListener(_update);
  }
  
  void _update() { _time += 0.016; setState(() {}); }

  
  void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('拍现象')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: BeatPainter(_freq1, _freq2, _time), size: Size.infinite)),
        _buildControls(),
      ]),
    );
  }

  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
      child: Column(mainAxisSize: MainAxisSize.min, children: [
        Row(children: [
          const Text('频率1: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _freq1, min: 1, max: 10, onChanged: (v) => setState(() => _freq1 = v))),
          Text('${_freq1.toStringAsFixed(1)} Hz', style: const TextStyle(color: Colors.orange)),
        ]),
        Row(children: [
          const Text('频率2: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _freq2, min: 1, max: 10, onChanged: (v) => setState(() => _freq2 = v))),
          Text('${_freq2.toStringAsFixed(1)} Hz', style: const TextStyle(color: Colors.orange)),
        ]),
        Text('拍频: ${(_freq1 - _freq2).abs().toStringAsFixed(2)} Hz', style: const TextStyle(color: Colors.white70)),
      ]),
    );
  }
}

class BeatPainter extends CustomPainter {
  final double freq1, freq2, time;
  BeatPainter(this.freq1, this.freq2, this.time);

  
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final omega1 = 2 * pi * freq1;
    final omega2 = 2 * pi * freq2;
    final beatFreq = (freq1 - freq2).abs();
    final avgFreq = (freq1 + freq2) / 2;
    final centerY = size.height / 2;
    final scale = size.height / 4;
    
    final envelopePath = Path();
    for (int i = 0; i <= size.width.toInt(); i++) {
      final t = time + i * 0.001;
      final env = 2 * cos(pi * beatFreq * t).abs() * scale;
      if (i == 0) envelopePath.moveTo(i.toDouble(), centerY - env);
      else envelopePath.lineTo(i.toDouble(), centerY - env);
    }
    for (int i = size.width.toInt(); i >= 0; i--) {
      final t = time + i * 0.001;
      final env = 2 * cos(pi * beatFreq * t).abs() * scale;
      envelopePath.lineTo(i.toDouble(), centerY + env);
    }
    envelopePath.close();
    canvas.drawPath(envelopePath, Paint()..color = Colors.orange.withOpacity(0.2));
    
    final wavePath = Path();
    for (int i = 0; i <= size.width.toInt(); i++) {
      final t = time + i * 0.001;
      final y = 2 * cos((omega1 - omega2) * t / 2) * sin((omega1 + omega2) * t / 2) * scale;
      if (i == 0) wavePath.moveTo(i.toDouble(), centerY - y);
      else wavePath.lineTo(i.toDouble(), centerY - y);
    }
    canvas.drawPath(wavePath, Paint()..color = Colors.orange..style = PaintingStyle.stroke..strokeWidth = 2);
  }

  
  bool shouldRepaint(covariant BeatPainter old) => true;
}

/// 双缝干涉演示
class DoubleSlitDemo extends StatefulWidget {
  const DoubleSlitDemo({super.key});
  
  State<DoubleSlitDemo> createState() => _DoubleSlitDemoState();
}

class _DoubleSlitDemoState extends State<DoubleSlitDemo> {
  double _slitSeparation = 50;
  double _wavelength = 20;
  double _screenDistance = 200;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('双缝干涉')),
      body: Column(children: [
        Expanded(child: CustomPaint(painter: DoubleSlitPainter(_slitSeparation, _wavelength, _screenDistance), size: Size.infinite)),
        _buildControls(),
      ]),
    );
  }

  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
      child: Column(mainAxisSize: MainAxisSize.min, children: [
        Row(children: [
          const Text('缝间距: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _slitSeparation, min: 20, max: 100, onChanged: (v) => setState(() => _slitSeparation = v))),
          Text(_slitSeparation.toStringAsFixed(0), style: const TextStyle(color: Colors.teal)),
        ]),
        Row(children: [
          const Text('波长: ', style: TextStyle(color: Colors.white70)),
          Expanded(child: Slider(value: _wavelength, min: 10, max: 50, onChanged: (v) => setState(() => _wavelength = v))),
          Text(_wavelength.toStringAsFixed(0), style: const TextStyle(color: Colors.teal)),
        ]),
        Text('条纹间距: ${(_wavelength * _screenDistance / _slitSeparation).toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70)),
      ]),
    );
  }
}

class DoubleSlitPainter extends CustomPainter {
  final double slitSeparation, wavelength, screenDistance;
  DoubleSlitPainter(this.slitSeparation, this.wavelength, this.screenDistance);

  
  void paint(Canvas canvas, Size size) {
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
    
    final centerX = size.width * 0.2;
    final centerY = size.height / 2;
    final screenX = size.width * 0.9;
    
    canvas.drawRect(Rect.fromLTWH(centerX - 5, 0, 10, centerY - slitSeparation / 2 - 5), Paint()..color = Colors.grey);
    canvas.drawRect(Rect.fromLTWH(centerX - 5, centerY - slitSeparation / 2 + 5, 10, slitSeparation - 10), Paint()..color = Colors.grey);
    canvas.drawRect(Rect.fromLTWH(centerX - 5, centerY + slitSeparation / 2 + 5, 10, size.height - centerY - slitSeparation / 2 - 5), Paint()..color = Colors.grey);
    
    for (int i = 0; i < 8; i++) {
      final angle = (i - 4) * 0.1;
      final endY = centerY + tan(angle) * (screenX - centerX);
      
      final path = Path();
      path.moveTo(centerX, centerY - slitSeparation / 2);
      path.lineTo(screenX, endY);
      canvas.drawPath(path, Paint()..color = Colors.teal.withOpacity(0.3)..style = PaintingStyle.stroke);
      
      final path2 = Path();
      path2.moveTo(centerX, centerY + slitSeparation / 2);
      path2.lineTo(screenX, endY);
      canvas.drawPath(path2, Paint()..color = Colors.teal.withOpacity(0.3)..style = PaintingStyle.stroke);
    }
    
    final k = 2 * pi / wavelength;
    for (int y = 0; y < size.height.toInt(); y++) {
      final dy = y - centerY;
      final pathDiff = sqrt(pow(screenX - centerX, 2) + pow(dy - slitSeparation / 2, 2)) -
                       sqrt(pow(screenX - centerX, 2) + pow(dy + slitSeparation / 2, 2));
      final intensity = cos(k * pathDiff / 2) * cos(k * pathDiff / 2);
      final hue = 180 + intensity * 60;
      canvas.drawLine(Offset(screenX - 30, y.toDouble()), Offset(screenX, y.toDouble()),
          Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, intensity).toColor()..strokeWidth = 1);
    }
  }

  
  bool shouldRepaint(covariant DoubleSlitPainter old) => false;
}

📝 四、数学原理深入解析

📐 4.1 波动方程推导

从牛顿定律到波动方程

考虑弦上的微小元素:

张力 T 沿弦方向
弦的线密度 μ

对于小位移 y,张力近似恒定

力的平衡:
T·sin(θ + Δθ) - T·sin(θ) = μ·Δx·∂²y/∂t²

小角度近似:sin(θ) ≈ tan(θ) = ∂y/∂x

T·(∂y/∂x|_{x+Δx} - ∂y/∂x|_x) = μ·Δx·∂²y/∂t²

两边除以 Δx,取极限:
T·∂²y/∂x² = μ·∂²y/∂t²

即:∂²y/∂t² = (T/μ)·∂²y/∂x²

波速:v = √(T/μ)

🔄 4.2 干涉条纹计算

双缝干涉光程差

设两缝间距为 d,屏幕距离为 D

对于屏幕上 y 处的点:
- 到上缝距离:r₁ = √(D² + (y - d/2)²)
- 到下缝距离:r₂ = √(D² + (y + d/2)²)

光程差:Δr = r₂ - r₁

当 D >> d, y 时:
Δr ≈ d·sin(θ) ≈ d·y/D

明条纹条件:Δr = nλ
y_n = nλD/d

暗条纹条件:Δr = (n + 1/2)λ
y_n = (n + 1/2)λD/d

条纹间距:Δy = λD/d

🌸 4.3 驻波能量分布

驻波的能量分析

驻波方程:y = 2A·sin(kx)·cos(ωt)

动能密度:
dk = ½ρ(∂y/∂t)² = 2ρA²ω²·sin²(kx)·sin²(ωt)

势能密度:
dp = ½T(∂y/∂x)² = 2TA²k²·cos²(kx)·cos²(ωt)

总能量密度:
dE = dk + dp

在波节处 (sin(kx) = 0):
- 动能密度 = 0
- 势能密度最大

在波腹处 (cos(kx) = 0):
- 动能密度最大
- 势能密度 = 0

能量在波节和波腹之间周期性转移

🎯 4.4 傅里叶分析

波的频谱分解

任何周期波都可以分解为简谐波的叠加:

y(t) = Σ Aₙ·cos(nω₀t + φₙ)

傅里叶系数:
Aₙ = (2/T)∫₀ᵀ y(t)·cos(nω₀t)dt
φₙ = arctan(...)

对于方波:
y(t) = (4A/π)·[sin(ω₀t) + sin(3ω₀t)/3 + sin(5ω₀t)/5 + ...]

只有奇次谐波,振幅按 1/n 衰减

🔬 五、高级应用场景

🎨 5.1 音频可视化

频谱分析器

class SpectrumAnalyzer {
  final int sampleRate;
  final int fftSize;
  
  SpectrumAnalyzer({required this.sampleRate, this.fftSize = 1024});
  
  List<double> analyze(List<double> samples) {
    final spectrum = List<double>.filled(fftSize ~/ 2, 0);
    
    for (int k = 0; k < fftSize ~/ 2; k++) {
      double real = 0, imag = 0;
      for (int n = 0; n < fftSize; n++) {
        final angle = 2 * pi * k * n / fftSize;
        real += samples[n] * cos(angle);
        imag -= samples[n] * sin(angle);
      }
      spectrum[k] = sqrt(real * real + imag * imag) / fftSize;
    }
    
    return spectrum;
  }
}

🌐 5.2 声学仿真

房间声学模拟

class RoomAcoustics {
  final double length, width, height;
  final double absorption;
  
  RoomAcoustics({required this.length, required this.width, required this.height, this.absorption = 0.3});
  
  List<double> calculateModes(int maxOrder) {
    final modes = <double>[];
    final c = 343.0;
    
    for (int nx = 0; nx <= maxOrder; nx++) {
      for (int ny = 0; ny <= maxOrder; ny++) {
        for (int nz = 0; nz <= maxOrder; nz++) {
          if (nx + ny + nz > 0) {
            final freq = c / 2 * sqrt(pow(nx / length, 2) + pow(ny / width, 2) + pow(nz / height, 2));
            modes.add(freq);
          }
        }
      }
    }
    
    modes.sort();
    return modes;
  }
  
  double reverberationTime() {
    final volume = length * width * height;
    final surfaceArea = 2 * (length * width + length * height + width * height);
    return 0.161 * volume / (surfaceArea * absorption);
  }
}

📱 5.3 鸿蒙多端适配

性能优化配置

class WaveSimulationConfig {
  static int getOptimalGridSize(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final minDim = min(size.width, size.height);
    return (minDim / 2).toInt();
  }
  
  static double getOptimalTimeStep() {
    return 0.016;
  }
}

📊 六、性能优化策略

⚡ 6.1 FFT 优化

快速傅里叶变换

class FFT {
  static List<Complex> fft(List<Complex> x) {
    final n = x.length;
    if (n <= 1) return x;
    
    final even = <Complex>[];
    final odd = <Complex>[];
    for (int i = 0; i < n; i += 2) {
      even.add(x[i]);
      if (i + 1 < n) odd.add(x[i + 1]);
    }
    
    final evenFFT = fft(even);
    final oddFFT = fft(odd);
    
    final result = List<Complex>.filled(n, Complex(0, 0));
    for (int k = 0; k < n ~/ 2; k++) {
      final t = Complex.fromPolar(1, -2 * pi * k / n) * oddFFT[k];
      result[k] = evenFFT[k] + t;
      result[k + n ~/ 2] = evenFFT[k] - t;
    }
    
    return result;
  }
}

class Complex {
  final double real, imag;
  Complex(this.real, this.imag);
  
  factory Complex.fromPolar(double r, double theta) => Complex(r * cos(theta), r * sin(theta));
  
  Complex operator +(Complex other) => Complex(real + other.real, imag + other.imag);
  Complex operator *(Complex other) => Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real);
}

💾 6.2 波形缓存

预计算优化

class WaveformCache {
  static final Map<String, List<double>> _cache = {};
  
  static List<double> getWaveform(String key, double wavelength, double amplitude, int points) {
    return _cache.putIfAbsent(key, () {
      return List.generate(points, (i) {
        final x = i * wavelength / points;
        return amplitude * sin(2 * pi * x / wavelength);
      });
    });
  }
}

🎓 七、学习资源与拓展

📚 推荐阅读

主题 资源 难度
波动理论 《物理学》- 哈里德 ⭐⭐
声学 《声学基础》 ⭐⭐⭐
光学 《光学原理》- 玻恩 ⭐⭐⭐
信号处理 《数字信号处理》 ⭐⭐⭐

🔗 相关项目

  • Web Audio API:浏览器音频处理
  • PortAudio:跨平台音频库
  • JUCE:音频应用框架

📝 八、总结

本篇文章深入探讨了声波干涉与驻波的数学原理及其在 Flutter 中的可视化实现。

✅ 核心知识点回顾

知识点 说明
🌊 波动方程 波的传播与叠加
🔀 叠加原理 多波干涉现象
驻波形成 波节与波腹分布
🎵 拍现象 频率差产生的调制
🔬 双缝干涉 杨氏实验原理

⭐ 最佳实践要点

  • ✅ 使用波动方程精确模拟
  • ✅ 合理设置波长和频率参数
  • ✅ 注意边界条件处理
  • ✅ 使用 FFT 加速频谱分析

🚀 进阶方向

  • 🔮 三维波动模拟
  • ✨ 实时音频处理
  • 📊 声场可视化
  • 🎵 音频效果器开发

💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐