Play audio the simplest way possible (from console)

Hello!

I’m working on a project where I need to generate sound and/or play sound files.
Tried to start an Audio App project from the Projucer, but I’m not very familiar with the syntax and structure of the code that was generated, so I thought that a consol project would be the simplest.

So now I started a new consol project, I got a simple main function which is nice, now I wonder
how I can play sound files with the least amount of code possible from this main function?

I’d also like to ask the same question regarding playing a sine tone from this main function.

I’m working on a project involving the Sensel Morph, which is a multi touch tablet. In the example they provide:
https://github.com/sensel/sensel-api-c-cpp/blob/master/examples/sensel_example_read_contacts/main.c

They scan for touches inside a while loop inside the main function. When I generated the Audio App project from Projucer, I didn’t get a main function, it’s all event driven so I couldn’t figure out where to put the code.

So now I’m trying it the other way around, instead of adapting the sensel code to the Juce code, I now want to adapt the Juce code to the sensel code. And for that purpose I need the simplest way possible to generate sounds!

Thanks in advance :slight_smile:

https://www.juce.com/doc/tutorial_playing_sound_files Does that help?

Hi,

the minimum is an AudioIODevice, you get it from the AudioDeviceManager.
Second you need an AudioSource of your choice, to play an audio file use AudioFormatReaderSource, or to generate a sine use ToneGeneratorAudioSource.
To bring them together you need an AudioIODeviceCallback, in that case an AudioSourcePlayer.

The absolute minimum I found:

#include "../JuceLibraryCode/JuceHeader.h"

int main (int argc, char* argv[])
{
    File input ("/Users/whoever/test.wav");
    AudioFormatManager fmgr;
    fmgr.registerBasicFormats();
    ScopedPointer<AudioFormatReaderSource> source =
        new AudioFormatReaderSource (fmgr.createReaderFor(input), true);

    AudioDeviceManager devmgr;
    devmgr.initialiseWithDefaultDevices (0, 2);
    AudioIODevice* device = devmgr.getCurrentAudioDevice();
    if (device && source) {
        source->prepareToPlay (device->getDefaultBufferSize(),
                               device->getCurrentSampleRate());
        ScopedPointer<AudioSourcePlayer> player = new AudioSourcePlayer ();
        player->setSource (source);
        devmgr.addAudioCallback (player);
        while (source->getNextReadPosition() < source->getTotalLength())
            Thread::sleep(100);
        return 0;
    }
    return -1;
}

Caveat: File will not accept relative paths…

Hope that helps

2 Likes

BTW. if you want to use console arguments, I wrote a handy options parser, helps to structure your code, checks for needed arguments, converts local paths, and creates a unix-like help text.
To be found on github and can be added as module via Projucer.

1 Like

Thanks daniel for the code! I tried it, but it gave me a breakpoint in juce_File.cpp at line 136.
Which is strange, I tried to place it under users and call “/Users/bongo.wav”, also I tried “C:/bongo.wav”, tried all sort of different variations: C:/ C:// C:\ C:\, they all gave me the same breakpoint.

What am I doing wrong?
Also, I’m curious why it only takes absolute paths and not relative?

Thanks in advance! You’re a lifesave if you help me get started. :slight_smile:

Thanks for the link! I will read it, and probably learn much, but I can’t use it directly to get started, since it involves buttons and browsing. I just need to play already chosen files, and later generate some tones.

It would help, if you post the text next to the breakpoint, it usually explains what happened :wink:
In this case I guess, that the path was not accepted. On windows, you use backslashes. But because they have a special meaning in C/C++ code, you have to quote them, that means preceding the backquote with a backquote.
Long story short, try:

File input ("C:\\bongo.wav");

And for building filenames later use File::getSpecialLocation() and from there File::getChildFile(), that way you won’t need to worry about slash or backslashes, and your code works on any plattform juce supports…

To make it work on any plattform. Some systems have one tree, others have drives with letters…
The JUCE-way is to start with a so called “special location”, e.g.

File input = File::getSpecialLocation (File::userHomeDirectory).getChildFile ("bongo.wav");

There are a many different predefined locations, see File::SpecialLocationType

Or you can work in the current directory:

File input = File::getCurrentWorkingDirectory().getChildFile ("bongo.wav");

Thank you Daniel! Thanks to you there’s progress at last.

I had to call CoInitialize to get past another breakpoint that was triggered at line devmgr.initialiseWithDefaultDevices(0, 2);

So this is what I have now! :slight_smile:

#include "../JuceLibraryCode/JuceHeader.h"
#include <comdef.h>

using namespace std;

//==============================================================================
int main (int argc, char* argv[]) {
	CoInitialize(0);

	File input("C:\\bongo.wav");
	AudioFormatManager fmgr;
	fmgr.registerBasicFormats();
	ScopedPointer<AudioFormatReaderSource> source = new AudioFormatReaderSource(fmgr.createReaderFor(input), true);

	AudioDeviceManager devmgr;
	devmgr.initialiseWithDefaultDevices(0, 2);
	AudioIODevice* device = devmgr.getCurrentAudioDevice();
	if (device && source) {
		source->prepareToPlay(device->getDefaultBufferSize(),
			device->getCurrentSampleRate());
		ScopedPointer<AudioSourcePlayer> player = new AudioSourcePlayer();
		player->setSource(source);
		devmgr.addAudioCallback(player);
		while (source->getNextReadPosition() < source->getTotalLength()) {
			Thread::sleep(100);
			
		}
		devmgr.closeAudioDevice();
	}

    return 0;
}

It played the sound successfully! :slight_smile:
Had to add devmgr.closeAudioDevice(); in the end, otherwise I got another exception.

How can I modify this code to play many sounds? Let’s say I want to play two different files at the same time, or with a bit of delay?

Thanks you VERY much again daniel!

Have a look at the AudioSource class, and especially at the inheritance diagram.
Most of these AudioSources can be plugged into each other. In your example you set a MixerAudioSource to the AudioSourcePlayer and connect various Sources to play. To control when to start which source, you may want to pipe it through AudioTransportSource, which features a play and stop functionality.

When your program starts to become interactive you should consider to use the GUI Application template. You don’t have to open a GUI, if you don’t want to. Simply remove the MainWindow. But that way a lot of Boilerplate code is added that makes life easier.

Why is, accessing an audio device, then creating an audio source, and then how to then pass data to the device not the “hello world” for JUCE? I think this alone was more helpful to understanding how the library actually does it’s thing than any other resource.