录音/制作/创作 吉他 扩声技术 视频技术 作品展示 生活 信息 更多... | 音频应用专卖店

肤浅揭秘软件录音和DSP编程

( 69 )
 
[收藏]
-  第 1 页  -

324
#1 11-4-4 07:19

肤浅揭秘软件录音和DSP编程

原本打算根据另一个主题帖子的内容和上下文语境,写一点立体声录音制式(MS)方面的文字,但是原帖已锁定,缺乏讨论环境,暂时先放到一边吧。换个话题,另开一贴。关于MS,稍后再谈。

肤浅揭秘软件录音和DSP编程

燕子李三(Liyan78)


【摘要】 见标题
【关键词】 数字录音,跨平台,编程,DSP,二阶IIR,马甲,喷子
【Key words】Digital Recording, Cross-platform, Programming, DSP, Second order Infinite Impulse Response, MaJia, PenZi


【题外话】
写作本文实属无奈,一把年纪的人了,还要做这种没有技术含量的苦力,自嘲一下。但既然写了,就希望能有一点点保留的价值。有人说这里是专业的权威论坛,既然专业,不妨超前一点,尽管所涉及的技术或技巧早已是熟透的烂瓜,妇孺皆知,根本不值一提。另一点,此处高人太多,血盆大口等着撕咬我这样的“捣蛋分子”和“马甲”,言多必失,为避免自取其辱,干脆少说别人说过的,多给实打实的东西,有人称赞我干打雷不下雨,建议我从简单的开始,没问题,就从最简单的开始。

不敢大言不惭全球别无分号,但力求所述内容前无来者。欢迎批评。如需转载,拿去即可。作者“燕子李三”,注明与否全凭良心。

【要写什么】
这里是录音版块,而且大部分话题都围绕电脑数字录音,那么就写写这个。用什么DAW?你out了,真正牛B之人肯定不屑于用别人的,那么就一定自己写。我不牛B,因此不得不把团队中的小郑喊了过来,让他帮忙整理一下前些年帮洋人码的一堆垃圾,中用的拣了一下,为跟上时代,显得自己时尚一些,比如跨平台,支持64位、Win 7、OSX、iPhone、Linux之类,就看了看有关资料,做了去芜存菁和更省事的改写。至于省了什么事,用什么玩意省的,懂的人一看便知,不懂的说破大天也白扯。你们这个圈子里真有高手,我也是高手的学生和受益者,但我本人并无义务帮大师扫盲,我只是你们认为的马甲和喷子。如有不适,请自便。

至于代码的行文风格,这完全是字如其人,我还没有修炼到随时可以……(已修改)等等高尚无私的人生境界,我一向认为,不过是网络论坛而已,尽管良心只有一颗,但是动辄光荣的劣币驱良,抱成一团,其实很傻很天真,更是很多事物发展多年也不过如此的根本原因。故此,比照大师,尽量原始。真要把百十万行拉丁字母都发上来,我没这个耐心。

【整体架构】
对不住,我们这样的马甲不懂架构,也没什么规划论证,项目分析之类,无非MIDI和Digital Audio,不是什么高屋建瓴的了不起,就一技术工种,贱的象狗一样,实在不值得鼻孔朝天。读书人贪贫不贪多,活着就为一个玩弄文字和乱喷,又让你们失望了。

【GUI部分】
无关痛痒的就不浪费版面了。又好看又关键的部分见下。先看“头”:
class TrackPanel  : public Component,
                    public ButtonListener,
                    public LabelListener,
                    public SliderListener,
                    public ComboBoxListener
{
public:   
    TrackPanel ();
    ~TrackPanel();
    void paint (Graphics& g);
    void resized();
    void buttonClicked (Button* buttonThatWasClicked);
    void labelTextChanged (Label* labelThatHasChanged);
    void sliderValueChanged (Slider* sliderThatWasMoved);
    void comboBoxChanged (ComboBox* comboBoxThatHasChanged);
void mouseDoubleClick (const MouseEvent& e);
// 其他方法暂略,这里都是大师,自然有更厉害的招数,献丑不如藏拙
    // 此处有个小宏就不明示了,但这个是必须的
private:

friend class TracksPanelComponent;
//友元一把再说

TracksPanelComponent* forTrackPanel;
    ToggleButton* slectButton;
    Label* trackNameLabel;
    Slider* volSlider;
//音量。都是一些可视化的小东西,下同
    Slider* panSlider;
//声像
    TextButton* muteButton;
//静音
    TextButton* soloButton;
//独奏
    MyComboBox* midiPortListBox;
//MIDI端口
    MyComboBox* midiChannelListBox;
//MIDI通道
    MyComboBox* bankListBox;
//音色库
    MyComboBox* patchListBox;
//音色
    MyComboBox* sendToListBox;
//发送
    Slider* keyShiftSlider;
//变调
    Slider* delaySlider;
//迟滞
    Slider* rebSlider;
//混响
Slider* choursSlider;
//合唱
//……还有一些玩意。我太懒,就算了吧。

    TrackPanel (const TrackPanel&);
//拷构
    const TrackPanel& operator= (const TrackPanel&);
//=重载
};

下面这个小类,没有它,在音轨面板上点鼠标右键会出问题。其实就是派生一个改写mouseDown方法的ComboBox,不让它出现鼠标右键打架的问题,没什么技巧性。其他控件同理。
class MyComboBox:public ComboBox
{
public:

MyComboBox(const String& componentName = String::empty):ComboBox(componentName)

{

}
// 这个构函纯属空城计

void mouseDown(const MouseEvent& e)

{

if (e.mods.isLeftButtonDown())

showPopup();
// 取消本身的右键下拉,代之以本部分的右键菜单

}
};

定义:暂略吧。主要是太长,更多只是布局和定位,没什么可研究的。

音轨区的GUI,这个复杂一些,由于功能和操作模式的差别,实现思路各不相同。下一步据说会出现更唬人的玩法,究竟如何,我现在也不太了解。为避免无谓的争议和打压,不给出了。

几句废话。GUI部分(包括所有一切),还可用Qt类库。但是这个类库不开源,生成后必须要打包所必须的几个DLL,会无端增大文件体积,我认为它并不适合部署小规模的项目。用于开发数字音频方面的程序,很多东西还要重新“造轮子”,很烦人。但是,Qt的优势不小,做界面很类似写网页(HTML编码),而且用户、资料、现成的东西都很多,比较成熟。

走带条以录音按钮为例吧,同时一并给出操作区中一个简单的音轨排列条(显示波形的),多轨的波形显示区域基本同理,但复杂很多,我这里删减了大量代码,按大师给出的DEMO示例的原始模样列出来。注意作用域类模板那个recoder对象,这个是本模块的核心,其类随后给出。
class AudioRecord  : public Component,
                             public ButtonListener
{
public:
    AudioRecord (AudioDeviceManager& deviceManager_);
    ~AudioRecord();
    void paint (Graphics& g);
    void resized();
    void buttonClicked (Button* buttonThatWasClicked);
//点击录音
    void visibilityChanged();
    //依然是那个小宏
private:
    AudioDeviceManager& deviceManager;
//本机所安装的音频设备
    ScopedPointer<AudioRecorder> recorder;
//作用域指针,录音模块核心
    LiveAudioInputDisplayComp* liveAudioDisplayComp;//实时输入显示组件
    Label* Label1;
    TextButton* recordButton;
// 录音按钮
    AudioRecord (const AudioDemoRecordPage&);//拷构
    const AudioDemoRecordPage& operator= (const AudioDemoRecordPage&);//=重载
};

播放,暂停,倒带之类,随便一个搞过多媒体开发的Coder都会做,不凑字了。MIDI部分,不符本版主题,略。
GUI部分还有更多,例如paint绘图之类。太复杂的东西一时间说不清,水平也有限,略吧。

324
#2 11-4-4 07:21
【音频录音模块】
这个核心模块的头和身子,一并给出。和那帮喜欢玩微软DS编程的人不同,我一向很讨厌微软的东西。下面的代码可能看起来很怪,不妨亲自试一下。
class AudioRecorder  : public AudioIODeviceCallback // 派生于本机音频设备回调类
{
public:
    AudioRecorder()
        : backgroundThread ("Audio Recorder Thread"),
          sampleRate (0), activeWriter (0) //初始化线程,采样率、写入
    {
        backgroundThread.startThread();
//线程启动
    }//========================
    ~AudioRecorder()
    {
        stop();
//析构时停止,猫腻啊
    }

    void startRecording (const File& file)
// 录音
    {
        stop();


        if (sampleRate > 0)
//采样率,判断,嵌套
        {
            // 创建输出流,数据写入文件
            file.deleteFile();
            ScopedPointer<FileOutputStream> fileStream (file.createOutputStream());

            if (fileStream != 0)
            {
                // 创建WAV writer对象,写入输出流
                WavAudioFormat wavFormat;
                AudioFormatWriter* writer = wavFormat.createWriterFor (fileStream, sampleRate, 1, 16, StringPairArray(), 0);

                if (writer != 0)
                {
                    fileStream.release();
                    threadedWriter = new AudioFormatWriter::ThreadedWriter (writer, backgroundThread, 32768);
                    const ScopedLock sl (writerLock);
                    activeWriter = threadedWriter;
                }
            }
        }
    }

    void stop()
//停止
    {
        {
            const ScopedLock sl (writerLock);
            activeWriter = 0;
        }
        threadedWriter = 0;

    }

    bool isRecording() const
// 当前是否处于录音状态
    {
        return activeWriter != 0;
    }

    void audioDeviceAboutToStart (AudioIODevice* device) //启动音频设备
    {
        sampleRate = device->getCurrentSampleRate();
    }

    void audioDeviceStopped()
//设备停止后采样率归位
    {
        sampleRate = 0;
    }

    void audioDeviceIOCallback (const float** inputChannelData, int /*numInputChannels*/,
                                float** outputChannelData, int numOutputChannels,
                                int numSamples) //设备回调
    {
        const ScopedLock sl (writerLock);

        if (activeWriter != 0)
            activeWriter->write (inputChannelData, numSamples);

        // 不管如何,一律先清空,腾出空间给输出缓冲。这是一个好习惯。
        for (int i = 0; i < numOutputChannels; ++i)
            if (outputChannelData != 0)
                zeromem (outputChannelData, sizeof (float) * numSamples);
    }

private:
    TimeSliceThread backgroundThread; // 数据写入磁盘时所用的线程
    ScopedPointer<AudioFormatWriter::ThreadedWriter> threadedWriter; // FIFO先进先出
    double sampleRate;
//音频采样率
    CriticalSection writerLock;
//写入锁定
    AudioFormatWriter::ThreadedWriter* volatile activeWriter;
//音频格式
};

【数据结构、算法、其他杂七杂八……】
有来有往吧。这几个简单的话题,大师不妨指点一下我这个马甲,您先开个头,我一定认真学习。

【ASIO和VST宿主】
ASIO SDK要到Steinberg官网注册一个ID,而后拉回来。VST有2.X和3版本。非常不好意思,本文所用的这个类库集成了这些玩意的接口,并给出了开关宏,因此,我懒得多说,也没什么好说的。真要说,请另外开贴。至于我是否回复,要看心情。

上面相当一批代码,可能有人会喷:这不是你李三造轮子,很多都是现成的,或者别人玩过的,不算高明,你还是披着德国皮、英国皮、米国皮的马甲。那好,我继续喷点复杂的,有料的,没皮没脸的。不再多说,也不再给出更多注释,希望各位大师能看明白里面的猫腻。

324
#3 11-4-4 07:22
【DSP滤波】
大多数IIR无限脉冲响应不具有线性相位,其实现比FIR有更多的灵活性,在这里给出几种基于IIR的DSP滤波(响应特性)。以下所有代码都有点乱,很多不该内联的也内联了,有待整理。

带系数的二阶IIR,万丈之基,无需多解释。总头和身子都不给了,给出小头:
struct BiquadPoleState;
class BiquadBase
{
public:
  template <class StateType>
  struct State : StateType { };
  complex_t response (double normalizedFrequency) const;
  std::vector<PoleZeroPair> getPoleZeros () const;
//标准库vector

  double getA0 () const { return m_a0; }
  double getA1 () const { return m_a1*m_a0; }
  double getA2 () const { return m_a2*m_a0; }
  double getB0 () const { return m_b0*m_a0; }
  double getB1 () const { return m_b1*m_a0; }
  double getB2 () const { return m_b2*m_a0; }
  template <class StateType, typename Sample>
  void process (int numSamples, Sample* dest, StateType& state) const
  {
    while (--numSamples >= 0)
      *dest++ = state.process (*dest, *this);
  }

protected:
  void setCoefficients (double a0, double a1, double a2,
                        double b0, double b1, double b2);
  void setOnePole (complex_t pole, complex_t zero);
  void setTwoPole (complex_t pole1, complex_t zero1,
                   complex_t pole2, complex_t zero2);
  void setPoleZeroPair (const PoleZeroPair& pair)
  {
    if (pair.isSinglePole ())
      setOnePole (pair.poles.first, pair.zeros.first);
    else
      setTwoPole (pair.poles.first, pair.zeros.first,
                  pair.poles.second, pair.zeros.second);
  }
  void setPoleZeroForm (const BiquadPoleState& bps);
  void setIdentity ();
  void applyScale (double scale);
public:
  double m_a0;
  double m_a1;
  double m_a2;
  double m_b1;
  double m_b2;
  double m_b0;
};

struct BiquadPoleState : PoleZeroPair
{
  BiquadPoleState () { }
  explicit BiquadPoleState (const BiquadBase& s);
  double gain;
};

class Biquad : public BiquadBase
{
public:
  Biquad ();
  explicit Biquad (const BiquadPoleState& bps);
  template <class StateType, typename Sample>
  void process (int numSamples,
                Sample* dest,
                StateType& state,
                Biquad sectionPrev) const
  {
    double t = 1. / numSamples;
    double da1 = (m_a1 - sectionPrev.m_a1) * t;
    double da2 = (m_a2 - sectionPrev.m_a2) * t;
    double db0 = (m_b0 - sectionPrev.m_b0) * t;
    double db1 = (m_b1 - sectionPrev.m_b1) * t;
    double db2 = (m_b2 - sectionPrev.m_b2) * t;

    while (--numSamples >= 0)
    {
      sectionPrev.m_a1 += da1;
      sectionPrev.m_a2 += da2;
      sectionPrev.m_b0 += db0;
      sectionPrev.m_b1 += db1;
      sectionPrev.m_b2 += db2;

      *dest++ = state.process (*dest, sectionPrev);
    }
  }

  template <class StateType, typename Sample>
  void process (int numSamples,
                Sample* dest,
                StateType& state,
                BiquadPoleState zPrev) const
  {
    BiquadPoleState z (*this);
    double t = 1. / numSamples;
    complex_t dp0 = (z.pole[0] - zPrev.pole[0]) * t;
    complex_t dp1 = (z.pole[1] - zPrev.pole[1]) * t;
    complex_t dz0 = (z.zero[0] - zPrev.zero[0]) * t;
    complex_t dz1 = (z.zero[1] - zPrev.zero[1]) * t;
    double dg = (z.gain - zPrev.gain) * t;

    while (--numSamples >= 0)
    {
      zPrev.pole[0] += dp0;
      zPrev.pole[1] += dp1;
      zPrev.zero[0] += dz0;
      zPrev.zero[1] += dz1;
      zPrev.gain += dg;

      *dest++ = state.process (*dest, Biquad (zPrev));
    }
  }

  void setOnePole (complex_t pole, complex_t zero)
  {
    BiquadBase::setOnePole (pole, zero);
  }

  void setTwoPole (complex_t pole1, complex_t zero1,
                   complex_t pole2, complex_t zero2)
  {
    BiquadBase::setTwoPole (pole1, zero1, pole2, zero2);
  }

  void setPoleZeroPair (const PoleZeroPair& pair)
  {
    BiquadBase::setPoleZeroPair (pair);
  }

  void applyScale (double scale)
  {
    BiquadBase::applyScale (scale);
  }
};

巴特沃斯(Butterworth)、切比雪夫(Chebyshev)等DSP滤波响应特性以后再给出吧,整理太耗时,实在太累,这些东西也未必适合大多数人的胃口。先写这么多,很混乱,题目太大,尚需更多的补充和修改。

好了,喷吧。

【参考文献】
[1] “JUCE (Jules' Utility Class Extensions)”, Julian Storer, 2011
[2] "A Collection of Useful C++ Classes for Digital Signal Processing", Vincent Falco, 2010
[3] “数字信号处理”,M.H. Hayes著,张延华 卓力译,科学出版社,2002
观众反应
:大哥,兄弟来晚了。

324
#4 11-4-4 07:55
【勘误】

[1] 第一页中的“生成后必须要打包所必须的几个动链”,多了一个“必须”,应为“生成后要打包所必须的几个动链”。
[2] 第三页带系数的二阶IIR基类,该头应给出C++命名空间。否则,使用其他类库时,可能会出现命名冲突。

【补充】

巴特沃斯(Butterworth)、切比雪夫(Chebyshev)等DSP滤波响应特性正在整理和改写,调试通过后发出。如果未发布,也请不要埋怨,最近俗事确实太多。再说我也不会写什么“网络教程”,写出来大家也都不明所以。其实这些都是真金白银,整理好了,都是大把的美金,直接就可以兑换的……

总之,这个文章发在这里,注定很失败……
//====================================
上述4月7日已发布。见本帖第33楼。

[ 本帖最后由 liyan78 于 11-4-7 21:24 编辑 ]

3591
#5 11-4-4 08:09
这个太专业了,还真看不懂

8218
#6 11-4-4 09:38
大哥……你太专业了……

1273
#7 11-4-4 09:52
真猛啊!!这是编程么?

7079
#8 11-4-4 10:07
这个真看不懂!顶了 支持一下!

704
#9 11-4-4 11:13
完全。。。不知所云。。。

5830
#10 11-4-4 12:09
深了深了
有点象给厨子讲解生物学

2081
#11 11-4-4 13:47
这文章,上面这些个,专业人士都不看懂, 那我更不用,瞎捣乱了。
我用吃中午饭的时间,顶你来了。
大哥,以后,多多写点儿东西吧,嘿嘿嘿,最好是对我们这这些,音频幼儿园级的人,有启发的
版主太抠了,只给加一个“十字标”,通快点儿,给个,10分多好。

253
#12 11-4-4 13:59
現在是r102  還在貼r73的舊版本

5395
#13 11-4-4 22:07
非常感谢楼主! 拜读几段受益匪浅,
顺便问个小问题,我这的intel编译器
上编译任何调用了juce的程序运行必
死机,连它自带的hellojuce都不行
请问您遇到过类似问题吗?
观众反应
CD3
:大师?

324
#14 11-4-5 01:28

回复 kennychi911 在 #12 的 pid=3040413 的贴子

谢谢观看。R102和R73是啥?

324
#15 11-4-5 01:37
原帖门子 于 11-4-4 22:07 发表
顺便问个小问题,我这的intel编译器
谢谢门子。
哪个平台下的Intel编译器?版本?

C++异常,基本运行时。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

搜索