简体   繁体   English

如何从JUCE演示音频插件主机访问音频数据?

[英]How to access Audio data from JUCE Demo Audio Plugin Host?

I am working on a project which requires me to record audio data as .wav files(of 1 second each) from a MIDI Synth plugin loaded in the JUCE Demo Audio Plugin host. 我正在做一个项目,要求我从JUCE演示音频插件主机中加载的MIDI Synth插件将音频数据记录为.wav文件(每个1秒)。 Basically, I need to create a dataset automatically (corresponding to different parameter configurations) from the MIDI Synth. 基本上,我需要从MIDI Synth自动创建一个数据集(对应于不同的参数配置)。

Will I have to send MIDI Note On/Off messages to generate audio data? 我是否需要发送MIDI Note On / Off信息来生成音频数据? Or is there a better way of getting audio data? 还是有更好的方式获取音频数据?

AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer) const

Is this the function which will solve my needs? 这是可以满足我需求的功能吗? If yes, how would I store the data? 如果是,我将如何存储数据? If not, could someone please guide me to the right function/solution. 如果没有,请有人指导我正确的功能/解决方案。 Thank you. 谢谢。

I'm not exactly sure what you're asking, so I'm going to guess: 我不确定您要问的是什么,所以我要猜测:

You need to programmatically trigger some MIDI notes in your synth, then write all the audio to a .wav file, right? 您需要以编程方式触发合成器中的一些MIDI音符,然后将所有音频写入.wav文件,对吗?

Assuming you already know JUCE, it would be fairly trivial to make an app that opens your plugin, sends MIDI, and records audio, but it's probably just easier to tweak the AudioPluginHost project. 假设您已经了解JUCE,那么制作一个用于打开插件,发送MIDI和录制音频的应用程序将是微不足道的,但是调整AudioPluginHost项目可能更容易。

Lets break it into a few simple steps (first open the AudioPluginHost project): 让我们将其分为几个简单步骤(首先打开AudioPluginHost项目):

  1. Programmatically send MIDI 以编程方式发送MIDI

Look at GraphEditorPanel.h , specifically the class GraphDocumentComponent . 查看GraphEditorPanel.h ,特别是GraphDocumentComponent类。 It has a private member variable: MidiKeyboardState keyState; 它具有一个私有成员变量: MidiKeyboardState keyState; . This collects incoming MIDI Messages and then inserts them into the incoming Audio & MIDI buffer that is sent to the plugin. 这将收集传入的MIDI消息,然后将它们插入到发送到插件的传入的音频和MIDI缓冲区中。

You can simply call keyState.noteOn (midiChannel, midiNoteNumber, velocity) and keyState.noteOff (midiChannel, midiNoteNumber, velocity) to trigger a note on. 您可以简单地调用keyState.noteOn (midiChannel, midiNoteNumber, velocity)keyState.noteOff (midiChannel, midiNoteNumber, velocity)来触发音符。

  1. Record Audio Output 记录音频输出

This is a fairly straightforward thing to do in JUCE — you should start by looking at the JUCE Demos. 在JUCE中这是一件非常简单的事情-您应该先查看JUCE演示。 The following example records output audio in the background, but there are plenty of other ways to do it: 下面的示例在后台记录输出音频,但是还有许多其他方法可以执行此操作:

class AudioRecorder  : public AudioIODeviceCallback
{
public:
    AudioRecorder (AudioThumbnail& thumbnailToUpdate)
        : thumbnail (thumbnailToUpdate)
    {
        backgroundThread.startThread();
    }

    ~AudioRecorder()
    {
        stop();
    }

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

        if (sampleRate > 0)
        {
            // Create an OutputStream to write to our destination file...
            file.deleteFile();
            ScopedPointer<FileOutputStream> fileStream (file.createOutputStream());

            if (fileStream.get() != nullptr)
            {
                // Now create a WAV writer object that writes to our output stream...
                WavAudioFormat wavFormat;
                auto* writer = wavFormat.createWriterFor (fileStream.get(), sampleRate, 1, 16, {}, 0);

                if (writer != nullptr)
                {
                    fileStream.release(); // (passes responsibility for deleting the stream to the writer object that is now using it)

                    // Now we'll create one of these helper objects which will act as a FIFO buffer, and will
                    // write the data to disk on our background thread.
                    threadedWriter.reset (new AudioFormatWriter::ThreadedWriter (writer, backgroundThread, 32768));

                    // Reset our recording thumbnail
                    thumbnail.reset (writer->getNumChannels(), writer->getSampleRate());
                    nextSampleNum = 0;

                    // And now, swap over our active writer pointer so that the audio callback will start using it..
                    const ScopedLock sl (writerLock);
                    activeWriter = threadedWriter.get();
                }
            }
        }
    }

    void stop()
    {
        // First, clear this pointer to stop the audio callback from using our writer object..
        {
            const ScopedLock sl (writerLock);
            activeWriter = nullptr;
        }

        // Now we can delete the writer object. It's done in this order because the deletion could
        // take a little time while remaining data gets flushed to disk, so it's best to avoid blocking
        // the audio callback while this happens.
        threadedWriter.reset();
    }

    bool isRecording() const
    {
        return activeWriter != nullptr;
    }

    //==============================================================================
    void audioDeviceAboutToStart (AudioIODevice* device) override
    {
        sampleRate = device->getCurrentSampleRate();
    }

    void audioDeviceStopped() override
    {
        sampleRate = 0;
    }

    void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels,
                                float** outputChannelData, int numOutputChannels,
                                int numSamples) override
    {
        const ScopedLock sl (writerLock);

        if (activeWriter != nullptr && numInputChannels >= thumbnail.getNumChannels())
        {
            activeWriter->write (inputChannelData, numSamples);

            // Create an AudioBuffer to wrap our incoming data, note that this does no allocations or copies, it simply references our input data
            AudioBuffer<float> buffer (const_cast<float**> (inputChannelData), thumbnail.getNumChannels(), numSamples);
            thumbnail.addBlock (nextSampleNum, buffer, 0, numSamples);
            nextSampleNum += numSamples;
        }

        // We need to clear the output buffers, in case they're full of junk..
        for (int i = 0; i < numOutputChannels; ++i)
            if (outputChannelData[i] != nullptr)
                FloatVectorOperations::clear (outputChannelData[i], numSamples);
    }

private:
    AudioThumbnail& thumbnail;
    TimeSliceThread backgroundThread  { "Audio Recorder Thread" }; // the thread that will write our audio data to disk
    ScopedPointer<AudioFormatWriter::ThreadedWriter> threadedWriter; // the FIFO used to buffer the incoming data
    double sampleRate   = 0.0;
    int64 nextSampleNum = 0;

    CriticalSection writerLock;
    AudioFormatWriter::ThreadedWriter* volatile activeWriter = nullptr;
};

Note that the actual audio callbacks that contain the audio data from your plugin are happening inside the AudioProcessorGraph inside FilterGraph . 请注意,包含来自插件的音频数据的实际音频回调发生在FilterGraphAudioProcessorGraph内部。 There is an audio callback happening many times a second where the raw audio data is passed in. It would probably be very messy to change that inside AudioPluginHost unless you know what you are doing — it would probably be simpler to use something like the above example or create your own app that has its own audio flow. 每秒有多次音频回调发生,原始音频数据被传入。在AudioPluginHost内更改此设置可能很麻烦,除非您知道自己在做什么–使用上面的示例可能会更简单或创建自己的具有自己音频流的应用。

The function you asked about: 您询问的功能:

AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer) const

is irrelevant. 是无关紧要的。 Once you're already in the audio callback, this would give you the audio being sent to a bus of your plugin (aka if your synth had a side chain). 一旦您已经进入音频回调,这将使您将音频发送到插件的总线(又名,如果您的合成器具有侧链)。 What you want to do instead is take the audio coming out of the callback and pass it to an AudioFormatWriter , or preferably an AudioFormatWriter::ThreadedWriter so that the actual writing happens on a different thread. 相反,您要做的是从回调中获取音频,并将其传递给AudioFormatWriter ,或者最好传递给AudioFormatWriter::ThreadedWriter以便实际写入发生在不同的线程上。

If you're not at all familiar with C++ or JUCE, Max/MSP or Pure Data might be easier for you to quickly whip something up. 如果您完全不熟悉C ++或JUCE,则Max / MSP或Pure Data可能会更容易使您快速掌握一些东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM