Hi folks, long time listener, first time caller.
I’m looking to create a plugin that, in addition to processing audio, logs its input and output level into a file, while running.
It doesn’t have to write every block – maybe every 1/3 of a second or so.
I’m very new to JUCE and cpp (though I have some experience in realtime). So I have a few questions:
- Where to initialize the logger?
- I’m thinking something like setCurrentLogger in the PluginProcessor constructor. What do you think?
- Where to write to log file?
- I’m told that writing to file in the audio thread is a bad idea. I agree, but I don’t know enough about JUCE to know where else I would put this.
- It would probably have some internal counter, and log every
n blocks; if that helps.
How to write from log to file?
Where/how to close the log file?
Thanks in advance!
Using the constructor seems reasonable, if you know which file you want to write. If you want the file to be configurable from the plugin editor, you might want to set the logger from the callback of a FileChooser, or after a text editor field is modified. That said, the JUCE Logger may not be a good fit for your use-case, and more general file-writing mechanisms might work better.
You could do something like this:
- Keep track of the current level on the audio thread. Every N samples, you can push the accumulated level into a queue (check out JUCE’s AbstractFifo for a wait-free implementation).
- Use a
Timer to read levels out of the fifo on the message thread, and write them to file.
It’s probably not worth using JUCE’s Logger in this scenario. You can just use
File::appendText to add some text to a file. Alternatively, you could use
FileOutputStream::writeText (this has the advantage that you only open and close the file once, rather than each time you write to the file).
This will happen automatically when using the methods I described above.
I just wanted to raise thoughts about the use case. If this is intended as a debug mode to tackle a problem (even when it is already rolled out to the customer) you might want to consider just logging on the audio thread as it saves you a hole lot of trouble.
That said: as reuk wrote a fifo queue populated by the audio thread and worked by the message thread or a different one is the safe way to go. When implementing the queue, make sure to push only the input/output value into the queue to avoid allocations. Don’t push the entire log message – that will allocate the string stuff on the heap.
Depending on your scenario take into consideration, that AbstractFifo only allows for one thread at a time to add stuff while one thread at a time removes/receives stuff. That might be an important constrain when you are multi threading your audio rendering.
Thank you both for your thoughtful responses.
I would need to look into the message thread, in this case. This is also new to me. I’ll look up the documentation – but I don’t suppose either of you could suggest some resources to get a better understanding of the message thread?
The message thread is the “main” thread, which will be used to process most of the events related to your plugin’s UI (mouse movement, keyboard input, window management etc.). It’s also the thread that will be used to run callbacks from Timer instances. The vast majority of Component member functions must be called from the message thread.
The message thread will always exist, and has fewer constraints than the realtime/audio thread. If you know that a particular operation is too expensive to run on the audio thread, but is still fairly fast (a few milliseconds), then it’s probably a good idea to run it on the message thread.
Thanks, so this is the thread that runs a lot of the “PluginEditor” stuff for example.
So, in a very practical sense, where would I literally put the
FileOutputStream::writeText call? In which script / class / method?
I’m guessing, e.g., that
resized() would be the wrong spots.
I really appreciate your help here. Thanks.
- Create a
juce::Timer (either derive your AudioProcessor from Timer, or create a whole new Timer object).
timerCallback. Inside this function, read from your FIFO, and write the values to file.
For an example of a similar pattern, take a look at the MidiLoggerPluginDemo example. In this example, there’s a queue of MIDI messages (MidiQueue). Messages are added to the queue in the plugin’s processBlock. The plugin’s AudioProcessor also implements Timer. In the timerCallback, any new messages are removed from the queue, and then used to update the UI.
For your use-case, you can do something very similar, but instead of writing MIDI messages to the queue, you can write the audio buffer amplitude; and instead of updating the UI, you can write directly to file.
Wonderful. This is a very helpful response. Thank you so much!