TransportAudioSourceMixerAudioSource

#1

I’m looking to have a MixerAudioSource to mix several TransportAudioSource

based on @daniel’s answer here, Multiple transportSources in AudioAppComponent? I think these need to hybridise a TransportMixerAudioSource

2 questions:

has anyone already done this and can share code?

is this the best way to have several different sources playing at once? - this will be in a Dynamic Library which will get a list of files of varying lengths which will get ‘play’ triggered at different points in time.

0 Likes

#2

How do you determine, when these are triggered?
If you read my answer in the thread before, you know, that I would advise against using multiple TransportAudioSources, because the method how they are triggered doesn’t lend itself to play synchronous in a deterministic manner.

If it is a kind of music instrument, have a look into Synthesiser and SamplerVoice.

If it is a kind of DAW, create a special audio source subclass (preferably PositionableAudioSource or AudioFormatReaderSource), where you can additionally specify the offsets and gains, and feed it into the MixerAudioSource.

0 Likes

#3

it’s kind of game-audio player, so the game will send transport triggers to the DLL

0 Likes

#4

So I take it, that you don’t mind, when exactly the sound starts to play, within an uncertainty of 100 ms, plus you don’t mind quantisation to an arbitrary grid of the beginnings of audio buffers.
This is owed to the audio buffers and the fact, that the start stop messages are processed by the message queue, which might be busy with drawing, loading a file or what ever non-realtime job is to do.

In that case using an array of TransportAudioSources is fine. Just plug them into a MixerAudioSource and you are good to go, there is nothing special about it.

0 Likes

#5

Yes, broadly speaking that’s OK.

I’m looking at small buffers so quantisation shouldn’t be too big an issue - and the reference will be the frame-rate of the game engine, which even if it was to render at 100fps shouldn’t be too laggy.

The message queue shouldn’t have any drawing etc to do, it’s a headless DLL.

But, for sake of argument, let’s say I wanted less uncertainty, what would be my approach then?

0 Likes

#6

The Synthesiser class addresses those issues, that you will face:

  • you will have the audio timestamped, i.e. counting samples and synchronise with that counter
  • you will need a way of setting the new sound in the audio thread, including with the information of the buffer offset
  • this is usually done via a fifo buffer

In your case I would attempt it with an AsyncUpdater for each source. You add the source to a list (using a SpinLock for adding to the container should be ok IMHO), and the async updater triggers the play/stop state. The benefit is, that it can be used in a thread safe manner, and it doesn’t use the message thread, which is in a DLL, problematic anyway, I’d assume.

1 Like

#7

how much processing can be done on individual sounds/voices? is this done in the renderNextBlock?

0 Likes

#8

sorry for being a pest on this!

Once an audioSource has been added to the mixer, is it just treated as a channel(s) of the mixerAudioSource ?

So if I added 1 mono file and 1 stereo it would mix them it would result in 3 input channels ?

0 Likes

#9

No worries, the handling of number of channels in AudioSources is a bit opaque, and I am also not 100% happy about it.

The last link in the chain (probably AudioSourcePlayer) determines, how many channels it wants by supplying an AudioBuffer in AudioSourceChannelInfo with a certain number of channels.

Each AudioSource feeding another one has it’s own means to change the number of channels. So better don’t expect anything clever here.

Usually if it has less channels than requested, it will copy the channels, and if it is more channels than requested, it will sum them (averaging).
But I am not 100% sure, if that is true for all AudioSource types.

And then there is ChannelRemappingAudioSource, where you can define your own mapping of channels.

0 Likes

#10

When I try to add and input to to the MixerAudioSource I’m getting error that MixerAudioSource has no member “addInputSource()”

AudioFormatManager formatManager;
OwnedArray<AudioFormatReaderSource> inputReaders;
ScopedPointer<MixerAudioSource> mixer;
AudioSourcePlayer player;

...

	for (auto audioFileToLoad : playlist) //playlist is a std::vector<File>
	{
		if (auto* reader = formatManager.createReaderFor(audioFileToLoad))
			inputReaders.add(new AudioFormatReaderSource(reader, true));

		else jassertfalse;
	}

	mixer = new MixerAudioSource();
	
	for (auto* reader : inputReaders)
		mixer.addInputSource(reader);

	player.setSource(mixer);
0 Likes

#11

addInputSource (AudioSource* source, bool deleteWhenNoLongerUsed) takes two arguments…

Since your OwnedArray owns the sources, you want to set the second argument to false…

0 Likes

#12

good spot but that isn’t solving the ‘no member’ issue

0 Likes

#13

Shouldn’t this be mixer->addInputSource(reader);

1 Like

#14

Oh right, sorry…

You want to use -> to access the MixerAudioSource, not the ScopedPointer itself

1 Like

#15

@cpr you beat me :wink:

0 Likes

#16

@daniel lol

0 Likes

#17

I got that to work, however, the two files I chose didn’t have the same sampleRate. At this point, I want to maintain this possibility.

So I attempted to chain the AudioFormatReaderSource into AudioTransportSource - so I can have both stop/start and the resampling method.

However, I hit an access violation exception in the MixerAudioSource::prepareToPlay when I call deviceManager.addAudioCallback

AudioFormatManager formatManager;
OwnedArray<AudioTransportSource> inputSources;
ScopedPointer<MixerAudioSource> mixer;
ScopedPointer<AudioTransportSource> audioTransportSource;
AudioSourcePlayer player;
AudioDeviceManager deviceManager;

	formatManager.registerBasicFormats();

	for (auto audioFileToLoad : playlist)
	{
		if (auto* reader = formatManager.createReaderFor(audioFileToLoad))
		{
				audioTransportSource = new AudioTransportSource();
				audioTransportSource->setSource(new AudioFormatReaderSource(reader, true), 0, nullptr, 0, reader->numChannels);
				inputSources.add(audioTransportSource);
			
		}
		else jassertfalse;
	}

	mixer = new MixerAudioSource();
	
	for (auto* source : inputSources) {
		mixer->addInputSource(source, false);
	}
		
	player.setSource(mixer);

	deviceManager.initialiseWithDefaultDevices(0, 2);
	deviceManager.addAudioCallback(&player);
	player.prepareToPlay(64, 96000); // temp hardcoded

the error is here

void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
    tempBuffer.setSize (2, samplesPerBlockExpected);

    const ScopedLock sl (lock);

    currentSampleRate = sampleRate;
    bufferSizeExpected = samplesPerBlockExpected;

    /*   ERROR when i = 0    */
    for (int i = inputs.size(); --i >= 0;)
        inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate);
}
0 Likes

#18

I don’t get, why people are so crazy about TransportAudioSource. It is really only suitable for a live player, not a plugin, and not to be combined with other TransportAudioSources.

Instead feed it through ResamplingAudioSource, that works reliable without the message thread.

The error, when there is no input into the MixerAudioSource seems like a genuine bug to me, so this loop might need some attention from the JUCE team.

0 Likes

#20

So I’m not making a plugin. I am making a live-player, of sorts, its being played, like a game so I’d like to have stop/start on the individual sources.

Thanks for your thoughts on the error.

1 Like

#21

Oh, sorry, I mixed that with another conversation, my bad.

I just am not a fan of TransportAudioSource, as you may have seen, because it is not clear, what drawbacks it brings when it is crossing the thread boundaries.
And when people started with the audio player tutorial, they are likely to try to use it in a plugin, and it works for a while, until somebody clicks bounce out.

But for your use case, it might be fine.

0 Likes