1

我一直在使用C#/WPF 桌面应用程序中出色的NAudio库开发专门的 mp3/wav/(wma 后?)播放器。我还添加了Skype Voice Changer中使用的 JSNet 库。

我的目标是能够在播放时修改声音文件的PanPitch

我在播放过程中成功修改了 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;
            }
        }
    }
}
4

1 回答 1

1

您必须记住effect.Slider()在滑块的值更改后调用。每当滑块更改时,许多效果都必须重新计算参数,因此出于性能原因,您必须在更改值后通知它。

我希望通过ISampleProvider界面重新实现我为 SkypeFx 制作的许多效果,这将使它们在 NAudio 中更容易使用。

于 2012-12-05T09:04:04.657 回答