Pump audio data from file into JACK

Programming applications for making music on Linux.

Moderators: MattKingUSA, khz

Post Reply
f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

Pump audio data from file into JACK

Post by f00bar »

I have a problem in how to play waveform data from a high-latency device (think disk file, network socket). I cannot call any blocking function from the realtime thread, so I cannot fetch data directly. I guess some kind of FIFO should work. The writing to the FIFO must block when the FIFO is full, but must not when testing for emptyness. The JACK ringbuffer does not block at all, so the file I/O thread will then need to busywait. What is the canonical solution to this problem.

Recording works easier, because I can let the realtime thread signal the I/O thread when a new data block is ready. Reversing that mechanism would not work, because
1) this requires blocking the realtime thread
2) the I/O thread is to fast, so it needs to be paused anyways.
User avatar
raboof
Established Member
Posts: 1855
Joined: Tue Apr 08, 2008 11:58 am
Location: Deventer, NL
Has thanked: 50 times
Been thanked: 74 times
Contact:

Re: Pump audio data from file into JACK

Post by raboof »

f00bar wrote:I have a problem in how to play waveform data from a high-latency device (think disk file, network socket). I cannot call any blocking function from the realtime thread, so I cannot fetch data directly. I guess some kind of FIFO should work. The writing to the FIFO must block when the FIFO is full, but must not when testing for emptyness. The JACK ringbuffer does not block at all, so the file I/O thread will then need to busywait. What is the canonical solution to this problem.
Good question, looking forward to the answers.

Not really answering your question, but of course you could simply read the whole waveform into memory before starting playback.

That's certainly easy, but of course might not work in your situation - it might introduce unacceptable latency while loading the waveform, or use an unacceptable amount of memory.
f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

Re: Pump audio data from file into JACK

Post by f00bar »

raboof wrote:
f00bar wrote:I have a problem in how to play waveform data from a high-latency device (think disk file, network socket). I cannot call any blocking function from the realtime thread, so I cannot fetch data directly. I guess some kind of FIFO should work. The writing to the FIFO must block when the FIFO is full, but must not when testing for emptyness. The JACK ringbuffer does not block at all, so the file I/O thread will then need to busywait. What is the canonical solution to this problem.
Good question, looking forward to the answers.

Not really answering your question, but of course you could simply read the whole waveform into memory before starting playback.

That's certainly easy, but of course might not work in your situation - it might introduce unacceptable latency while loading the waveform, or use an unacceptable amount of memory.
Dump it into memory first works for small amounts of data. That is what Hydrogen does, and I used that approach in Anja. But now I want to play longer files and then I need a streaming solution.
tramp
Established Member
Posts: 2347
Joined: Mon Jul 01, 2013 8:13 am
Has thanked: 9 times
Been thanked: 466 times

Re: Pump audio data from file into JACK

Post by tramp »

Using 2 fixed size buffers, big enough, and switch them between disk and realtime thread.
To say, read data in the first buffer, hand it over to the rt-thread. As soon the rt-thread starts to read the buffer, signal the disk thread to fill the other buffer. Switch them, when the rt-thread have reach the end of the first buffer, . . .
Here is how I use it for record, but the same approve, just in turn, could work for play.
https://github.com/brummer10/screcord.l ... record1.cc
On the road again.
f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

Re: Pump audio data from file into JACK

Post by f00bar »

tramp wrote:Using 2 fixed size buffers, big enough, and switch them between disk and realtime thread.
To say, read data in the first buffer, hand it over to the rt-thread. As soon the rt-thread starts to read the buffer, signal the disk thread to fill the other buffer. Switch them, when the rt-thread have reach the end of the first buffer, . . .
Here is how I use it for record, but the same approve, just in turn, could work for play.
https://github.com/brummer10/screcord.l ... record1.cc
The problem is how to determine the current buffer. I have

Code: Select all

void Jasmine::Impl::writeByChannel(const float* data,unsigned int n_frames
	,unsigned int n_channels_in,unsigned int channel_out_first)
	{
	auto buffer=m_buffers_in[0].get();
	buffer->readyWait();
	printf("Writing to %p\n",buffer);
	buffer->writeByChannel(data,n_frames,n_channels_in,channel_out_first);
	}

inline int Jasmine::Impl::dataProcess(jack_nframes_t N) noexcept
	{
	//	Write data to output port
		{
		auto buffer_in_front=m_buffers_in[1].get();
		auto ports_out_begin=m_ports_out.data();
		auto ports_out_end=ports_out_begin + m_ports_out.size();
		auto ch=0;
		while( ports_out_begin!=ports_out_end )
			{
			float* buffer_out=static_cast<float*>
				(jack_port_get_buffer(*ports_out_begin,N));

			buffer_in_front->read(buffer_out,N,ch);
			++ch;
			++ports_out_begin;
			}
		buffer_in_front->frameOffsetAdvance(N);
		if(buffer_in_front->done())
			{
			std::swap(m_buffers_in[0],m_buffers_in[1]);
			m_buffers_in[1].get()->readySet();
			}
		}

	return 0;
	}
When the I/O thread calls `writeByChannel` the second time, the buffer is still the same, since the RT-thread has not yet switched over. So the output is

Code: Select all

Writing to 0x21eaef0
Writing to 0x21eaef0
Writing to 0x21eae60
Writing to 0x21eae60
Writing to 0x21eaef0
Writing to 0x21eae60
rather than

Code: Select all

Writing to 0x21eaef0
Writing to 0x21eae60
Writing to 0x21eaef0
Writing to 0x21eae60
Writing to 0x21eaef0
Writing to 0x21eae60
Also the two first writes must go without any wait, otherwise it wont fill the buffer in time, introducing a periodic popping sound.
tramp
Established Member
Posts: 2347
Joined: Mon Jul 01, 2013 8:13 am
Has thanked: 9 times
Been thanked: 466 times

Re: Pump audio data from file into JACK

Post by tramp »

What I mean is, switch the buffer and signal the disc thread to fill the second buffer, when the rt-thread start to read the first buffer, not when it's finished. Choose the buffer size big enough to have a couple of jack-frame sizes in it, to give the disc thread more time to fill the buffer.
When you switch the buffer in the rt-thread, you could assume, that it is ready, as the disc thread fill a big chunk of data faster (at once) then the jack thread needs to play them.
This introduced a latency (playback wont start immediately when you press start), but, for playback files, that shouldn't matter.
On the road again.
f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

Re: Pump audio data from file into JACK

Post by f00bar »

tramp wrote:What I mean is, switch the buffer and signal the disc thread to fill the second buffer, when the rt-thread start to read the first buffer, not when it's finished. Choose the buffer size big enough to have a couple of jack-frame sizes in it, to give the disc thread more time to fill the buffer.
When you switch the buffer in the rt-thread, you could assume, that it is ready, as the disc thread fill a big chunk of data faster (at once) then the jack thread needs to play them.
This introduced a latency (playback wont start immediately when you press start), but, for playback files, that shouldn't matter.
Swapping and signaling before data outut seems to work
Post Reply