我一直在使用C#/WPF 桌面应用程序中出色的NAudio库开发专门的 mp3/wav/(wma 后?)播放器。我还添加了Skype Voice Changer中使用的 JSNet 库。
我的目标是能够在播放时修改声音文件的Pan和Pitch。
我在播放过程中成功修改了 Pan 属性。我还想出了如何使用 JSNet SuperPitch 对象来修改音高。但是,我只设法在播放之前修改音高。播放开始后,SuperPitch 对象上的控件似乎对声音没有影响。
有没有人成功地将使用 SuperPitch 的音高控制和平移控制与 WaveChannel32.Pan 结合起来?
问题源于这样一个事实,即效果应用于 a WaveStream
,然后将其转换为 aWaveChannel32
以公开.Pan
属性。但是,一旦发生到 WaveChannel32 的转换,EffectChain
似乎就不再连接了。
这是一个精简的 WPF 项目,它说明了我的基本方法:
简单的 WPF 窗口:
<Window x:Class="NAudioDebug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Slider x:Name="sldPan" TickFrequency="0.2" Minimum="-1.0" Maximum="1.0" ToolTip="Balance"
Grid.Row="0" TickPlacement="Both" IsSnapToTickEnabled="True"
Value="{Binding Path=Pan, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Slider x:Name="sldPitch" TickFrequency="1" Minimum="-12" Maximum="12.0" ToolTip="Pitch"
Grid.Row="1" TickPlacement="Both" IsSnapToTickEnabled="True"
Value="{Binding Path=Pitch, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="btnPlayStop" Content="Play/Stop" Grid.Row="2" Click="btnPlayStop_Click" />
</Grid>
</Window>
这是上面 XAML 的代码隐藏...
using System.Windows;
namespace NAudioDebug
{
public partial class MainWindow : Window
{
private Player _Player;
public MainWindow()
{
InitializeComponent();
_Player = new Player();
this.DataContext = _Player;
}
private void btnPlayStop_Click(object sender, RoutedEventArgs e)
{
_Player.PlayStop();
}
}
}
声音文件播放对象
代码的重要部分是这个对象,它控制播放并提供 WPF 控件可以绑定到的属性。我认为PlayStop()
方法是问题所在。
using System;
using System.ComponentModel;
using JSNet;
using NAudio.Wave;
namespace NAudioDebug
{
public class Player : INotifyPropertyChanged, IDisposable
{
private IWavePlayer _WaveOutDevice;
private WaveChannel32 _MainOutputStream;
private EffectStream _EffectStream;
private EffectChain _Effects;
private SuperPitch _PitchEffect;
private bool _bDisposed = false;
public Player()
{
Pan = 0.0f;
Pitch = 0.0f;
}
private float _Pan;
public float Pan
{
get { return _Pan; }
set
{
_Pan = value;
if (_MainOutputStream != null)
{
_MainOutputStream.Pan = _Pan;
}
OnPropertyChanged("Pan");
}
}
private float _Pitch;
public float Pitch
{
get { return _Pitch; }
set
{
_Pitch = value;
if (_PitchEffect != null)
{
_PitchEffect.Sliders[1].Value = value; // Slider 1 is the pitch bend in semitones from -12 to +12;
}
OnPropertyChanged("Pitch");
}
}
public void PlayStop()
{
if (_WaveOutDevice != null && _WaveOutDevice.PlaybackState == PlaybackState.Playing)
{
_WaveOutDevice.Stop();
DisposeAudioResources();
}
else
{ // Starting a new stream...
DisposeAudioResources();
_Effects = new EffectChain();
_PitchEffect = new SuperPitch();
_PitchEffect.Sliders[1].Value = _Pitch; // Slider 1 is the pitch bend in semitones from -12 to +12;
_PitchEffect.Sliders[2].Value = 0.0F; // Slider 2 is the pitch bend in octaves.
_Effects.Add(_PitchEffect);
WaveStream inputStream; // NOTE: Use a WaveStream here because the input might be .mp3 or .wav (and later .wma?)
WaveStream mp3Reader = new Mp3FileReader(@"C:\Temp\Test.mp3");
if (mp3Reader.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
{
mp3Reader = WaveFormatConversionStream.CreatePcmStream(mp3Reader);
}
inputStream = mp3Reader;
_EffectStream = new EffectStream(_Effects, inputStream);
_MainOutputStream = new WaveChannel32(_EffectStream);
_MainOutputStream.PadWithZeroes = false;
_MainOutputStream.Pan = Pan;
_WaveOutDevice = new WaveOut();
_WaveOutDevice.Init(_MainOutputStream);
_WaveOutDevice.Play();
this._WaveOutDevice.PlaybackStopped += OnPlaybackStopped;
}
}
private void OnPlaybackStopped(object sender, EventArgs e)
{ // Clean up the audio stream resources...
DisposeAudioResources();
}
private void DisposeAudioResources()
{
if (_WaveOutDevice != null) { _WaveOutDevice.Stop(); }
if (_MainOutputStream != null) { _MainOutputStream.Close(); _MainOutputStream = null; }
if (_PitchEffect != null) { _PitchEffect = null; }
if (_Effects != null) { _Effects = null; }
if (_EffectStream != null) { _EffectStream = null; }
if (_WaveOutDevice != null) { _WaveOutDevice.Dispose(); _WaveOutDevice = null; }
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) { handler(this, e); }
}
protected void OnPropertyChanged(string sPropertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(sPropertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool bDisposing)
{ // Check to see if Dispose has already been called...
if (!_bDisposed)
{
DisposeAudioResources();
_bDisposed = true;
}
}
}
}