GTK3/poll complex multithreading issue

54 views Asked by At

I am coding a fully functional synthesizer on LINUX.

Right now it is able to detect a midi keyboard and play from it with customizable sound,for sound management I am using SDL2_audio with its callback functionality. But unfortunately the GUI is totally absent. Therefor I started with something simple, and decided to make a visualizer, that would take a table of floats from my stream and draw it on a gtk drawing area.

I found some code that corresponded to what I needed in it's simplest form, and after some adaptation, this is what it looks like.

#include <gtk/gtk.h>
#include <math.h>
#include <cairo.h>

#define WIDTH 1080
#define HEIGHT 1080
#define N_THREADS 1
#define N_ITERATIONS 100000

#define ZOOM_X 100.0
#define ZOOM_Y 100.0

GMainContext *context;



static gboolean
on_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
{
    GdkRectangle da;            /* GtkDrawingArea size */
    gdouble dx = 1.055, dy = 1.055; /* Pixels between each point */
    gdouble i, clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;

    GdkWindow *window = gtk_widget_get_window(widget);

    /* Determine GtkDrawingArea dimensions */
    gdk_window_get_geometry(window,
                            &da.x,
                            &da.y,
                            &da.width,
                            &da.height);

    /* Draw on a black background */
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
    cairo_paint(cr);

    /* Change the transformation matrix */
    cairo_translate(cr, da.width / 2, da.height / 2);
    cairo_scale(cr, ZOOM_X, -ZOOM_Y);

    /* Determine the data points to calculate (ie. those in the clipping zone */
    cairo_device_to_user_distance(cr, &dx, &dy);
    cairo_clip_extents(cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
    cairo_set_line_width(cr, dx);

    /* Draws x and y axis */
    cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
    cairo_move_to(cr, clip_x1, 0.0);
    cairo_line_to(cr, clip_x2, 0.0);
    cairo_move_to(cr, 0.0, clip_y1);
    cairo_line_to(cr, 0.0, clip_y2);
    cairo_stroke(cr);

    float *us = (float *)user_data;
    //printf("exec x1 %f , x2 %f, dx %f\n", clip_x1, clip_x2, dx);

    
    /* Link each data point */
    int cpt = 0;
    for (i = clip_x1; i < clip_x2; i += dx)
    {

        float he = us[cpt];
        // printf("double %f\n",i);
        cairo_line_to(cr, i, he);
        cpt += 1;
    }
    //printf("cpt %d\n", cpt);

    /* Draw the curve */
    cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.6);
    cairo_stroke(cr);

    int drawing_area_width = gtk_widget_get_allocated_width(widget);
    int drawing_area_height = gtk_widget_get_allocated_height(widget);

    gtk_widget_queue_draw_area(widget,0,0,drawing_area_width,drawing_area_height);

    return G_SOURCE_REMOVE;
}

void new_sig_calc(float *signal)
{
    for (int i = 0; i < 1024; i++)
    {
        float x = (float)rand() / (float)(RAND_MAX / 4);
        signal[i] = sinf((float)(i + x) * 0.05f + x);
    }
}


static gpointer
thread_func(gpointer user_data)
{
    GSource *source;
    float * table = (float *)user_data;

    g_print("Starting thread %d\n", 1);

    for (int n = 0; n < N_ITERATIONS; ++n) {
        
        new_sig_calc(table);
        /*
        source = g_idle_source_new();
        g_source_set_callback(source, G_SOURCE_FUNC(on_draw), (gpointer)table, NULL);
        g_source_attach(source, context);
        g_source_unref(source);
        */
    }
    g_print("Ending thread %d\n", 1);
    return NULL;
}




int main(int argc, char **argv)
{

    GtkWidget *window;
    GThread *thread[N_THREADS];
    GtkWidget *da;

    float *sig = malloc(sizeof(float) * 1024);

    gtk_init(&argc, &argv);
    
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(window), WIDTH, HEIGHT);
    gtk_window_set_title(GTK_WINDOW(window), "Graph drawing");
    

    da = gtk_drawing_area_new();
    gtk_container_add(GTK_CONTAINER(window), da);

    
    g_signal_connect(G_OBJECT(window), "destroy", gtk_main_quit, NULL);
    g_signal_connect(G_OBJECT(da),"draw",G_CALLBACK(on_draw),sig);
    
    context = g_main_context_default();

    for (int n = 0; n < N_THREADS; ++n)
        thread[n] = g_thread_new(NULL, thread_func, (gpointer)sig);

    gtk_widget_show_all(window);
    gtk_main();

    for (int n = 0; n < N_THREADS; ++n)
        g_thread_join(thread[n]);
    
    free(sig);
    return 0;
}

It works just fine, knowing that the testing signal is calculated in the new_sig_calc() function.

But here is where the big problem comes: My main program (the one that allows for midi playback) has an infinite loop in it, in which the midi events are detected by poll, and where all the important updates are made. But because of the way that gtk is made, it too acts like an infinite loop...

Here is what it looks like:

// Main loop of the app
void run_app(int running, ud *data,  Uint8 *state, int argc, char *argv[])
{
    data->fout = open_WAV("Bach.wav");
    data->fout_size = findSize("Bach.wav");
    struct pollfd *pfds;
    int npfds;
    int err;

    init_seq();

    if (parse_input(argc, argv) != -1)
    {
        printf("Parsing error\n");
    }

    snd_seq_t * seq = create_port();
    int port_count = connect_ports(seq);

    if (port_count > 0)
        printf("Waiting for data.");
    else
        printf("Waiting for data at port %d:0.",
               snd_seq_client_id(seq));
    printf(" Press Ctrl+C to end.\n");
    printf("Source  Event                  Ch  Data\n");

    signal(SIGINT, sighandler);
    signal(SIGTERM, sighandler);

    npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
    pfds = alloca(sizeof(*pfds) * npfds);

    while (running)
    {
        snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
        int p = poll(pfds, npfds, 20);
        if (p == 0)
        {
            update_effects(data);
            note_state(state, data);
            init_piano_keys(state, data);
        }
        else
        {
            if (p < 0)
            {
                break;
            }
            snd_seq_event_t *event_;
            err = snd_seq_event_input(seq, &event_);
            if (event_)
            {
                dump_event(event_, state);
                update_effects(data);
                note_state(state, data);
                init_piano_keys(state, data);
            }
        }
    }
}

So I can't quite get the two to work together. The idea I had is putting the whole gtk app in a thread and my main app in the other, and therefore running them at the same time. How can I make this work?

P.S. Here is the link to the github project: https://github.com/VladimirMeshcheriakov/VAMYTune.git

0

There are 0 answers