現代 UI 不僅是視覺,聽覺回饋能大幅提升使用者體驗 (拖曳音效、操作確認音、無障礙提示)。WPF 雖無內建合成器,但透過 NAudio (CodePlex/GitHub) 可輕鬆實現**運行時波形合成**,無需外部音檔。
Install-Package NAudio (或下載 1.3.8 版 DLL)using NAudio.Wave;waveOut* API,管理輸出裝置、緩衝區、回調。WaveFormat (採樣率、聲道) 與 Read(byte[] buffer, int offset, int count) 填入波形資料。short[] 而非 byte[]。class SineWaveOscillator : WaveProvider16
{
private double _phaseAngle;
public double Frequency { get; set; } = 440; // A4
public short Amplitude { get; set; } = 8192; // 音量
public SineWaveOscillator(int sampleRate = 44100) : base(sampleRate, 1) {} // Mono
public override int Read(short[] buffer, int offset, int sampleCount)
{
for (int i = 0; i < sampleCount; i++)
{
buffer[offset + i] = (short)(Amplitude * Math.Sin(_phaseAngle));
_phaseAngle += 2 * Math.PI * Frequency / WaveFormat.SampleRate;
if (_phaseAngle > 2 * Math.PI) _phaseAngle -= 2 * Math.PI;
}
return sampleCount;
}
}
// 播放啟動
var waveOut = new WaveOut();
var osc = new SineWaveOscillator(44100);
waveOut.Init(osc);
waveOut.Play(); // 非阻塞,背景執行緒持續呼叫 Read()
// 停止
osc.Amplitude = 0; // 靜音
waveOut.Stop();
waveOut.Dispose();
WaveOut 預設緩衝區 300ms (3 個 100ms 緩衝區)。Read() 在 **UI 執行緒** 執行,填滿整個緩衝區期間頻率無法變更 → 聽起來像「半音階跳躍」而非平滑滑音。
// MIDI Note 69 = A4 = 440Hz
// 每半音頻率比 = 2^(1/12)
public double Pitch
{
get => 69 + 12 * Math.Log(Frequency / 440, 2);
set => Frequency = 440 * Math.Pow(2, (value - 69) / 12);
}
優點:人耳感知為線性,拖曳距離映射音高極自然。
class PortamentoSineWaveOscillator : WaveProvider16
{
private double _phaseAngle;
private double _currentPitch, _targetPitch;
private int _samplesRemainingInGlide;
public double Pitch { get => _targetPitch; set => _targetPitch = value; }
public override int Read(short[] buffer, int offset, int sampleCount)
{
// 若音高改變,計算本緩衝區內的插值步長
if (_currentPitch != _targetPitch)
{
_samplesRemainingInGlide = sampleCount; // 簡化:單緩衝區完成滑音
}
for (int i = 0; i < sampleCount; i++)
{
// 線性插值音高
if (_samplesRemainingInGlide > 0)
{
double t = 1.0 - (double)_samplesRemainingInGlide / sampleCount;
double pitch = _currentPitch + (_targetPitch - _currentPitch) * t;
double freq = 440 * Math.Pow(2, (pitch - 69) / 12);
_phaseAngle += 2 * Math.PI * freq / WaveFormat.SampleRate;
_samplesRemainingInGlide--;
}
else
{
_phaseAngle += 2 * Math.PI * Frequency / WaveFormat.SampleRate;
}
if (_phaseAngle > 2 * Math.PI) _phaseAngle -= 2 * Math.PI;
buffer[offset + i] = (short)(Amplitude * Math.Sin(_phaseAngle));
}
_currentPitch = _targetPitch;
return sampleCount;
}
}
var waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()); // 背景執行緒回調
// 或
var waveOut = new WaveOut(WaveCallbackInfo.NewWindow()); // 隱藏視窗訊息迴圈
此時 Read() 在背景執行緒執行,UI 執行緒可隨時修改 Pitch,滑音極順暢。
Pitch = 60 + 12 * distance / 200 (每 200 像素升一個八度)原始碼下載:文章附件或 NAudio 官方範例
WaveProvider16 實作 Mixer:多個 IWaveProvider 輸入 → 加權加總 → 輸出合成波形。MidiOut、MidiEvent 類別,可發送 MIDI 訊息給系統合成器/外部裝置,播放樂器音色。| 項目 | 建議 |
|---|---|
| 採樣率 | 44.1kHz (CD 品質) 或 48kHz (專業音訊) |
| 緩衝區大小 | 延遲 < 50ms 覺得即時;DesiredLatency 設 100-200ms 平衡穩定度 |
| 平台 | 僅限 Windows (Win32 waveOut);**不可用於 Silverlight / UWP / .NET Core 跨平台** |
| 授權 | NAudio: MIT License,商業可用 |
WPF 配合 NAudio 即時合成波形,打破「應用程式應保持無聲」的傳統框架。從簡單的正弦波到複雜的加成合成、MIDI 控制,聲音成為 UI 互動的新維度。掌握 IWaveProvider、緩衝區管理與執行緒模型,即可為應用賦予豐富的聽覺體驗。