Re: [LAD] some questions about writing a jack client

From: Gabriel M. Beddingfield <gabriel@email-addr-hidden>
Date: Sat Nov 28 2009 - 17:25:36 EET

Hi Mutil,

On Thu, 26 Nov 2009, mutil wrote:

> The application is a dj-style (wannabe) app like mixxx, virtualdj etc. so it
> needs to decode in realtime a portion of the song, apply gain, fx etc and pass
> it to jack with as low latency as it can get.
>
> Currently, there are two readers (threads of high priority) where the decoding
> happens, which write the samples to two buffers. These two buffers, gets mixed
> and written (in a highest priority thread) to another buffer which is the one
> that feeds jack.
> I am sure there is a flaw with this design but I would like some more info on
> what is wrong and which is the suitable design.

The decoder thread seems fine. The mixer thread seems unnecc. I
would think that you really only want to apply your mixer value just
before output... as opposed to pre-mixing everything. I
could see doing this if, for instance, your goal is to apply
a setting to the entire session... but be able to listen to
it while it's still working. But that (to me) doesn't make
sense for a DJ-style application where you plan to change
things on-the-fly.

Here's a template for what you might do. There's no error
checking, and I'm sure there are critical thinkos. :-)

struct SongData {
     float *head;
     float *play_pos;
     float *write_pos;
     size_t size;
};

SongData track_1, track_2; /* Allocated somewhere else */
jack_port_t out_port; /* Initialized somewhere else */

int process(jack_nframes_t nframes, void* arg)
{
     float *end_1, *end_2, *out_buf;
     jack_nframes_t k;

     out_buf = jack_port_get_buffer(out_port, nframes);
     assert(out_buf);

     gain_1 = get_gain_track_1();
     gain_2 = get_gain_track_2();

     /* Assuming track_N.write_pos - track_N.play_pos
      * is always >= nframes. In real life, you'll
      * need to check for this.
      */
     for(k=0 ; k<nframes ; ++k) {
         out_buf[k] = track_1.play_pos[k] * gain_1
                      + track_2.play_pos[k] * gain_2;
         if( out_buf[k] > 1.0 ) out_buf[k] = 1.0;
         if( out_buf[k] < -1.0 ) out_buf[k] = -1.0;
     }

     return 0;
}

> Also it needs an extra thread to decode the whole song, so I can get the
> waveform, the BPM etc, but I think this can be done in a lower priority
> thread.

A high-priority thread is fine... you just have to ensure that your
application is buffered sufficiently so that you always have enough
data to play. If not... fill with zeros or something.

> And some side-questions: where does the midi thread comes in (does it need
> a separate one)? And at what point should the fx(ladspa/lv2) be processed?

If you're using JACK MIDI, you don't have to allocate a separate
thread. It's just another jack_port_t pointer that you use inside of
your process().

As for FX processing... that usually gets done inside process() as
well. With LADSPA, you would do something like this to insert an
effect between track_1 and your final mixdown:

     float* fx_out_1; /* memory allocated elsewhere */
     LADSPA_Descriptor foomatic_echo;
     LADSPA_Handle fx_instance;

     foomatic_echo->connect(fx_instance, input_port_number, track_1.play_pos);
     foomatic_echo->connect(fx_instance, output_port_number, fx_out_1);
     foomatic_echo->run(instance, nframes);

     /* Now your mixdown looks like this... */
     for(k=0 ; k<nframes ; ++k) {
         out_buf[k] = fx_out_1[k] * gain_1
                      + track_2.play_pos[k] * gain_2;
         if( out_buf[k] > 1.0 ) out_buf[k] = 1.0;
         if( out_buf[k] < 1.0 ) out_buf[k] = -1.0;
     }

I hope this helps!

-gabriel
_______________________________________________
Linux-audio-dev mailing list
Linux-audio-dev@email-addr-hidden
http://lists.linuxaudio.org/mailman/listinfo/linux-audio-dev
Received on Sat Nov 28 20:15:01 2009

This archive was generated by hypermail 2.1.8 : Sat Nov 28 2009 - 20:15:02 EET