Beispiel #1
0
static void refresh_audio_clip_segments(AudioGraph *ag) {
    for (int clip_i = 0; clip_i < ag->audio_clip_list.length(); clip_i += 1) {
        AudioGraphClip *clip = ag->audio_clip_list.at(clip_i);
        clip->audio_clip->userdata = clip;
        clip->events_write_ptr = clip->events.write_begin();
        clip->events_write_ptr->clear();
    }

    auto it = ag->project->audio_clip_segments.entry_iterator();
    for (;;) {
        auto *entry = it.next();
        if (!entry)
            break;

        AudioClipSegment *segment = entry->value;
        AudioClip *audio_clip = segment->audio_clip;
        AudioGraphClip *clip = (AudioGraphClip *)audio_clip->userdata;
        assert(clip);
        ok_or_panic(clip->events_write_ptr->add_one());
        GenesisMidiEvent *event = &clip->events_write_ptr->last();
        event->event_type = GenesisMidiEventTypeSegment;
        event->start = segment->pos;
        event->data.segment_data.start = segment->start;
        event->data.segment_data.end = segment->end;
    }

    for (int i = 0; i < ag->audio_clip_list.length(); i += 1) {
        AudioGraphClip *clip = ag->audio_clip_list.at(i);
        clip->events.write_end();
        clip->events_write_ptr = nullptr;
    }
}
Beispiel #2
0
static AudioGraph *audio_graph_create_common(Project *project, GenesisContext *genesis_context,
        double latency)
{
    GenesisPipeline *pipeline;
    ok_or_panic(genesis_pipeline_create(genesis_context, &pipeline));

    genesis_pipeline_set_latency(pipeline, latency);
    genesis_pipeline_set_sample_rate(pipeline, project->sample_rate);
    genesis_pipeline_set_channel_layout(pipeline, &project->channel_layout);

    AudioGraph *ag = ok_mem(create_zero<AudioGraph>());
    ag->project = project;
    ag->pipeline = pipeline;
    ag->play_head_pos = 0.0;
    ag->is_playing = false;
    ag->play_head_changed_flag.clear();

    ag->resample_descr = genesis_node_descriptor_find(ag->pipeline, "resample");
    if (!ag->resample_descr)
        panic("unable to find resampler");

    genesis_pipeline_set_underrun_callback(pipeline, underrun_callback, ag);

    project->events.attach_handler(EventProjectAudioClipsChanged,
            on_project_audio_clips_changed, ag);
    project->events.attach_handler(EventProjectAudioClipSegmentsChanged,
            on_project_audio_clip_segments_changed, ag);


    refresh_audio_clips(ag);
    refresh_audio_clip_segments(ag);

    return ag;

}
Beispiel #3
0
 inline void append_double(double value) {
     static_assert(sizeof(double) == 8, "require double to be IEEE754 64-bit floating point");
     ok_or_panic(_buffer.resize(_buffer.length() + 8));
     double *ptr = (double *)(_buffer.raw() + _buffer.length() - 9);
     *ptr = value;
     _buffer.at(_buffer.length() - 1) = 0;
 }
Beispiel #4
0
static void refresh_audio_clips(AudioGraph *ag) {
    Project *project = ag->project;
    int ag_i = 0;
    int project_i = 0;
    for (;;) {
        AudioGraphClip *ag_clip = nullptr;
        AudioClip *project_clip = nullptr;
        if (ag_i < ag->audio_clip_list.length())
            ag_clip = ag->audio_clip_list.at(ag_i);
        if (project_i < project->audio_clip_list.length())
            project_clip = project->audio_clip_list.at(project_i);
        if (ag_clip && project_clip && ag_clip->audio_clip == project_clip) {
            ag_i += 1;
            project_i += 1;
        } else if (project_clip && !ag_clip) {
            ag_clip = ok_mem(create_zero<AudioGraphClip>());
            ag_clip->audio_clip = project_clip;
            ag_clip->audio_graph = ag;
            add_nodes_to_audio_clip(ag, ag_clip);
            ok_or_panic(ag->audio_clip_list.append(ag_clip));
            ag_i += 1;
            project_i += 1;
        } else if (!project_clip && ag_clip) {
            panic("TODO destroy audio graph clip");
            ag->audio_clip_list.swap_remove(ag_i);
        } else if (!project_clip && !ag_clip) {
            break;
        } else {
            panic("TODO replace nodes");
        }
    }
}
Beispiel #5
0
void SelectWidget::append_choice(const String &choice) {
    ok_or_panic(items.add_one());
    SelectWidgetItem *item = &items.last();
    item->parent = this;
    item->name = choice;
    item->menu_item = context_menu->add_menu(choice, -1, no_shortcut());
    set_activate_handlers();
    clear_context_menu();
}
Beispiel #6
0
void audio_graph_play_audio_asset(AudioGraph *ag, AudioAsset *audio_asset) {
    int err;
    if ((err = project_ensure_audio_asset_loaded(ag->project, audio_asset))) {
        if (err == GenesisErrorDecodingAudio) {
            fprintf(stderr, "Error decoding audio\n");
            return;
        } else {
            ok_or_panic(err);
        }
    }

    play_audio_file(ag, audio_asset->audio_file, true);
}
Beispiel #7
0
void TabWidget::insert_tab(Widget *widget, const String &title, int dest_index) {
    assert(!widget->parent_widget); // widget already has parent
    widget->parent_widget = this;

    TabWidgetTab *tab = create<TabWidgetTab>();
    tab->widget = widget;
    tab->label = create<Label>(gui);
    tab->label->set_text(title);
    tab->label->update();

    ok_or_panic(tabs.insert_space(dest_index, 1));
    tabs.at(dest_index) = tab;
    clamp_current_index();
}
Beispiel #8
0
void TabWidget::move_tab(int source_index, int dest_index) {
    assert(dest_index >= 0);
    assert(dest_index <= tabs.length());

    TabWidgetTab *target_tab = tabs.at(source_index);

    tabs.remove_range(source_index, source_index + 1);
    if (source_index < dest_index)
        dest_index -= 1;

    ok_or_panic(tabs.insert_space(dest_index, 1));

    tabs.at(dest_index) = target_tab;
    current_index = dest_index;
    clamp_current_index();
}
Beispiel #9
0
void settings_file_clear_dock(SettingsFileDock *dock) {
    ok_or_panic(dock->tabs.resize(0));
    switch (dock->dock_type) {
        case SettingsFileDockTypeTabs:
            break;
        case SettingsFileDockTypeHoriz:
        case SettingsFileDockTypeVert:
            if (dock->child_a) {
                settings_file_clear_dock(dock->child_a);
                destroy(dock->child_a, 1);
                dock->child_a = nullptr;
            }
            if (dock->child_b) {
                settings_file_clear_dock(dock->child_b);
                destroy(dock->child_b, 1);
                dock->child_b = nullptr;
            }
            break;
    }
    dock->dock_type = SettingsFileDockTypeTabs;
}
Beispiel #10
0
static void add_audio_node_to_audio_clip(AudioGraph *ag, AudioGraphClip *clip) {
    assert(!clip->node_descr);
    assert(!clip->node);

    ok_or_panic(project_ensure_audio_asset_loaded(ag->project, clip->audio_clip->audio_asset));
    GenesisAudioFile *audio_file = clip->audio_clip->audio_asset->audio_file;

    const struct SoundIoChannelLayout *channel_layout =
        genesis_audio_file_channel_layout(audio_file);
    int sample_rate = genesis_audio_file_sample_rate(audio_file);

    const char *name = "audio_clip";
    char *description = create_formatted_str("Audio Clip: %s",
            clip->audio_clip->name.encode().raw());
    GenesisNodeDescriptor *node_descr = ok_mem(genesis_create_node_descriptor(ag->pipeline, 2, name, description));
    free(description); description = nullptr;

    genesis_node_descriptor_set_userdata(node_descr, clip);

    struct GenesisPortDescriptor *audio_out_port = genesis_node_descriptor_create_port(
            node_descr, 0, GenesisPortTypeAudioOut, "audio_out");

    struct GenesisPortDescriptor *events_in_port = genesis_node_descriptor_create_port(
            node_descr, 1, GenesisPortTypeEventsIn, "events_in");

    if (!audio_out_port || !events_in_port)
        panic("unable to create ports");

    genesis_audio_port_descriptor_set_channel_layout(audio_out_port, channel_layout, true, -1);
    genesis_audio_port_descriptor_set_sample_rate(audio_out_port, sample_rate, true, -1);

    genesis_node_descriptor_set_run_callback(node_descr, audio_clip_node_run);
    genesis_node_descriptor_set_seek_callback(node_descr, audio_clip_node_seek);
    genesis_node_descriptor_set_create_callback(node_descr, audio_clip_node_create);
    genesis_node_descriptor_set_destroy_callback(node_descr, audio_clip_node_destroy);

    clip->node_descr = node_descr;
    clip->node = ok_mem(genesis_node_descriptor_create_node(node_descr));
}
Beispiel #11
0
static int on_string(struct LaxJsonContext *json,
    enum LaxJsonType type, const char *value_raw, int length)
{
    SettingsFile *sf = (SettingsFile *) json->userdata;
    ByteBuffer value = ByteBuffer(value_raw, length);
    switch (sf->state) {
        case SettingsFileStateReadyForProp:
            {
                if (ByteBuffer::compare(value, "open_project_id") == 0) {
                    sf->state = SettingsFileStateOpenProjectFile;
                } else if (ByteBuffer::compare(value, "user_name") == 0) {
                    sf->state = SettingsFileStateUserName;
                } else if (ByteBuffer::compare(value, "user_id") == 0) {
                    sf->state = SettingsFileStateUserId;
                } else if (ByteBuffer::compare(value, "perspectives") == 0) {
                    sf->state = SettingsFileStatePerspectives;
                } else if (ByteBuffer::compare(value, "open_windows") == 0) {
                    sf->state = SettingsFileStateOpenWindows;
                } else if (ByteBuffer::compare(value, "sample_dirs") == 0) {
                    sf->state = SettingsFileStateExpectSampleDirs;
                } else if (ByteBuffer::compare(value, "latency") == 0) {
                    sf->state = SettingsFileStateLatency;
                } else {
                    return parse_error(sf, "invalid setting name");
                }
                break;
            }
        case SettingsFileStateOpenProjectFile:
            sf->open_project_id = uint256::parse(value);
            sf->state = SettingsFileStateReadyForProp;
            break;
        case SettingsFileStateUserId:
            sf->user_id = uint256::parse(value);
            sf->state = SettingsFileStateReadyForProp;
            break;
        case SettingsFileStateUserName:
            {
                bool ok;
                sf->user_name = String(value, &ok);
                if (!ok)
                    return parse_error(sf, "invalid UTF-8");
                sf->state = SettingsFileStateReadyForProp;
                break;
            }
        case SettingsFileStatePerspectivesItemProp:
            if (ByteBuffer::compare(value, "name") == 0) {
                sf->state = SettingsFileStatePerspectivesItemPropName;
            } else if (ByteBuffer::compare(value, "dock") == 0) {
                sf->state = SettingsFileStatePerspectivesItemPropDock;
                sf->current_dock = &sf->current_perspective->dock;
            } else {
                return parse_error(sf, "invalid perspective property");
            }
            break;
        case SettingsFileStatePerspectivesItemPropName:
            {
                bool ok;
                sf->current_perspective->name = String(value, &ok);
                if (!ok)
                    return parse_error(sf, "invalid UTF-8");
                sf->state = SettingsFileStatePerspectivesItemProp;
                break;
            }
        case SettingsFileStateDockItemProp:
            if (ByteBuffer::compare(value, "dock_type") == 0) {
                sf->state = SettingsFileStateDockItemPropType;
            } else if (ByteBuffer::compare(value, "split_ratio") == 0) {
                sf->state = SettingsFileStateDockItemPropSplitRatio;
            } else if (ByteBuffer::compare(value, "child_a") == 0) {
                sf->state = SettingsFileStateDockItemPropChildA;
            } else if (ByteBuffer::compare(value, "child_b") == 0) {
                sf->state = SettingsFileStateDockItemPropChildB;
            } else if (ByteBuffer::compare(value, "tabs") == 0) {
                sf->state = SettingsFileStateDockItemPropTabs;
            } else {
                return parse_error(sf, "invalid dock item property name");
            }
            break;
        case SettingsFileStateDockItemPropType:
            if (ByteBuffer::compare(value, "Horiz") == 0) {
                sf->current_dock->dock_type = SettingsFileDockTypeHoriz;
            } else if (ByteBuffer::compare(value, "Vert") == 0) {
                sf->current_dock->dock_type = SettingsFileDockTypeVert;
            } else if (ByteBuffer::compare(value, "Tabs") == 0) {
                sf->current_dock->dock_type = SettingsFileDockTypeTabs;
            } else {
                return parse_error(sf, "invalid dock_type value");
            }
            sf->state = SettingsFileStateDockItemProp;
            break;
        case SettingsFileStateSampleDirsItem:
            ok_or_panic(sf->sample_dirs.append(value));
            break;
        case SettingsFileStateTabName:
            {
                bool ok;
                String title = String(value, &ok);
                if (!ok)
                    return parse_error(sf, "invalid UTF-8");
                ok_or_panic(sf->current_dock->tabs.append(title));
                break;
            }
        case SettingsFileStateOpenWindowItemProp:
            if (ByteBuffer::compare(value, "perspective") == 0) {
                sf->state = SettingsFileStateOpenWindowPerspectiveIndex;
            } else if (ByteBuffer::compare(value, "left") == 0) {
                sf->state = SettingsFileStateOpenWindowLeft;
            } else if (ByteBuffer::compare(value, "top") == 0) {
                sf->state = SettingsFileStateOpenWindowTop;
            } else if (ByteBuffer::compare(value, "width") == 0) {
                sf->state = SettingsFileStateOpenWindowWidth;
            } else if (ByteBuffer::compare(value, "height") == 0) {
                sf->state = SettingsFileStateOpenWindowHeight;
            } else if (ByteBuffer::compare(value, "maximized") == 0) {
                sf->state = SettingsFileStateOpenWindowMaximized;
            } else if (ByteBuffer::compare(value, "always_show_tabs") == 0) {
                sf->state = SettingsFileStateOpenWindowAlwaysShowTabs;
            } else {
                return parse_error(sf, "invalid open window property name");
            }
            break;
        default:
            return parse_error(sf, (type == LaxJsonTypeProperty) ? "unexpected property" : "unexpected string");
    }
    return 0;
}
Beispiel #12
0
int ordered_map_file_open(const char *path, OrderedMapFile **out_omf) {
    *out_omf = nullptr;
    OrderedMapFile *omf = create_zero<OrderedMapFile>();
    if (!omf) {
        ordered_map_file_close(omf);
        return GenesisErrorNoMem;
    }
    if (omf->queue.error() || omf->cond.error() || omf->mutex.error()) {
        ordered_map_file_close(omf);
        return omf->queue.error() || omf->cond.error() || omf->mutex.error();
    }
    omf->list = create_zero<List<OrderedMapFileEntry *>>();
    if (!omf->list) {
        ordered_map_file_close(omf);
        return GenesisErrorNoMem;
    }

    omf->map = create_zero<HashMap<ByteBuffer, OrderedMapFileEntry *, ByteBuffer::hash>>();
    if (!omf->map) {
        ordered_map_file_close(omf);
        return GenesisErrorNoMem;
    }

    omf->running = true;
    int err = omf->write_thread.start(run_write, omf);
    if (err) {
        ordered_map_file_close(omf);
        return err;
    }

    bool open_for_writing = false;
    omf->file = fopen(path, "rb+");
    if (omf->file) {
        int err = read_header(omf);
        if (err == GenesisErrorEmptyFile) {
            open_for_writing = true;
        } else if (err) {
            ordered_map_file_close(omf);
            return err;
        }
    } else {
        open_for_writing = true;
    }
    if (open_for_writing) {
        omf->file = fopen(path, "wb+");
        if (!omf->file) {
            ordered_map_file_close(omf);
            return GenesisErrorFileAccess;
        }
        int err = write_header(omf);
        if (err) {
            ordered_map_file_close(omf);
            return err;
        }
    }

    // read everything into list
    omf->write_buffer.resize(TRANSACTION_METADATA_SIZE);
    omf->transaction_offset = UUID_SIZE;
    for (;;) {
        size_t amt_read = fread(omf->write_buffer.raw(), 1, TRANSACTION_METADATA_SIZE, omf->file);
        if (amt_read != TRANSACTION_METADATA_SIZE) {
            // partial transaction. ignore it and we're done.
            break;
        }
        uint8_t *transaction_ptr = (uint8_t*)omf->write_buffer.raw();
        int transaction_size = read_uint32be(&transaction_ptr[4]);

        omf->write_buffer.resize(transaction_size);
        transaction_ptr = (uint8_t*)omf->write_buffer.raw();

        size_t amt_to_read = transaction_size - TRANSACTION_METADATA_SIZE;
        amt_read = fread(&transaction_ptr[TRANSACTION_METADATA_SIZE], 1, amt_to_read, omf->file);
        if (amt_read != amt_to_read) {
            // partial transaction. ignore it and we're done.
            break;
        }
        uint32_t computed_crc = crc32(0, &transaction_ptr[4], transaction_size - 4);
        uint32_t crc_from_file = read_uint32be(&transaction_ptr[0]);
        if (computed_crc != crc_from_file) {
            // crc check failed. ignore this transaction and we're done.
            break;
        }

        int put_count = read_uint32be(&transaction_ptr[8]);
        int del_count = read_uint32be(&transaction_ptr[12]);

        int offset = TRANSACTION_METADATA_SIZE;
        for (int i = 0; i < put_count; i += 1) {
            int key_size = read_uint32be(&transaction_ptr[offset]); offset += 4;
            int val_size = read_uint32be(&transaction_ptr[offset]); offset += 4;

            OrderedMapFileEntry *entry = create_zero<OrderedMapFileEntry>();
            if (!entry) {
                ordered_map_file_close(omf);
                return GenesisErrorNoMem;
            }

            entry->key = ByteBuffer((char*)&transaction_ptr[offset], key_size); offset += key_size;
            entry->offset = omf->transaction_offset + offset;
            entry->size = val_size;
            offset += val_size;

            auto old_hash_entry = omf->map->maybe_get(entry->key);
            if (old_hash_entry) {
                OrderedMapFileEntry *old_entry = old_hash_entry->value;
                destroy(old_entry, 1);
            }

            omf->map->put(entry->key, entry);
        }
        for (int i = 0; i < del_count; i += 1) {
            int key_size = read_uint32be(&transaction_ptr[offset]); offset += 4;
            ByteBuffer key((char*)&transaction_ptr[offset], key_size); offset += key_size;

            auto hash_entry = omf->map->maybe_get(key);
            if (hash_entry) {
                OrderedMapFileEntry *entry = hash_entry->value;
                omf->map->remove(key);
                destroy(entry, 1);
            }
        }

        omf->transaction_offset += transaction_size;

    }

    // transfer map to list and sort
    auto it = omf->map->entry_iterator();
    if (omf->list->ensure_capacity(omf->map->size())) {
        ordered_map_file_close(omf);
        return GenesisErrorNoMem;
    }
    for (;;) {
        auto *map_entry = it.next();
        if (!map_entry)
            break;

        ok_or_panic(omf->list->append(map_entry->value));
    }
    omf->map->clear();
    destroy_map(omf);

    omf->list->sort<compare_entries>();

    *out_omf = omf;
    return 0;
}
Beispiel #13
0
void os_open_in_browser(const String &url) {
    List<ByteBuffer> args;
    ok_or_panic(args.append(url.encode()));
    os_spawn_process("xdg-open", args, true);
}
Beispiel #14
0
GuiWindow *Gui::create_generic_window(bool with_borders, int left, int top, int width, int height) {
    GuiWindow *window = create<GuiWindow>(this, with_borders, left, top, width, height);
    window->_gui_index = _window_list.length();
    ok_or_panic(_window_list.append(window));
    return window;
}
Beispiel #15
0
 inline void append_uint64be(uint64_t value) {
     ok_or_panic(_buffer.resize(_buffer.length() + 8));
     char *ptr = _buffer.raw() + _buffer.length() - 9;
     write_uint64be(ptr, value);
     _buffer.at(_buffer.length() - 1) = 0;
 }
Beispiel #16
0
 void clear() {
     _buffer.clear();
     ok_or_panic(_buffer.append(0));
 }
Beispiel #17
0
static int on_begin(struct LaxJsonContext *json, enum LaxJsonType type) {
    SettingsFile *sf = (SettingsFile *) json->userdata;
    switch (sf->state) {
        default:
            return parse_error(sf, (type == LaxJsonTypeObject) ? "unexpected object" : "unexpected array");
        case SettingsFileStateStart:
            if (type != LaxJsonTypeObject)
                return parse_error(sf, "expected object");
            sf->state = SettingsFileStateReadyForProp;
            break;
        case SettingsFileStateExpectSampleDirs:
            if (type != LaxJsonTypeArray)
                return parse_error(sf, "expected array");
            sf->state = SettingsFileStateSampleDirsItem;
            break;
        case SettingsFileStatePerspectives:
            if (type != LaxJsonTypeArray)
                return parse_error(sf, "expected array");
            sf->state = SettingsFileStatePerspectivesItem;
            break;
        case SettingsFileStatePerspectivesItem:
            if (type != LaxJsonTypeObject)
                return parse_error(sf, "expected object");
            ok_or_panic(sf->perspectives.add_one());
            sf->current_perspective = &sf->perspectives.last();
            sf->state = SettingsFileStatePerspectivesItemProp;
            break;
        case SettingsFileStatePerspectivesItemPropDock:
            if (type != LaxJsonTypeObject)
                return parse_error(sf, "expected object");
            sf->state = SettingsFileStateDockItemProp;
            break;
        case SettingsFileStateDockItemPropTabs:
            if (type != LaxJsonTypeArray)
                return parse_error(sf, "expected array");
            sf->state = SettingsFileStateTabName;
            break;
        case SettingsFileStateDockItemPropChildA:
            ok_or_panic(sf->dock_stack.append(sf->current_dock));
            sf->current_dock->child_a = ok_mem(create_zero<SettingsFileDock>());
            sf->current_dock = sf->current_dock->child_a;
            sf->state = SettingsFileStateDockItemProp;
            break;
        case SettingsFileStateDockItemPropChildB:
            ok_or_panic(sf->dock_stack.append(sf->current_dock));
            sf->current_dock->child_b = ok_mem(create_zero<SettingsFileDock>());
            sf->current_dock = sf->current_dock->child_b;
            sf->state = SettingsFileStateDockItemProp;
            break;
        case SettingsFileStateOpenWindows:
            if (type != LaxJsonTypeArray)
                return parse_error(sf, "expected array");
            sf->state = SettingsFileStateOpenWindowItem;
            break;
        case SettingsFileStateOpenWindowItem:
            if (type != LaxJsonTypeObject)
                return parse_error(sf, "expected object");
            ok_or_panic(sf->open_windows.add_one());
            sf->current_open_window = &sf->open_windows.last();
            sf->state = SettingsFileStateOpenWindowItemProp;
            break;
    }
    return 0;
}
Beispiel #18
0
void audio_graph_start_pipeline(AudioGraph *ag) {
    int err;

    ag->start_play_head_pos = ag->play_head_pos;

    if (genesis_pipeline_is_running(ag->pipeline))
        return;

    int target_sample_rate = genesis_pipeline_get_sample_rate(ag->pipeline);
    SoundIoChannelLayout *target_channel_layout = genesis_pipeline_get_channel_layout(ag->pipeline);

    int audio_file_node_count = ag->audio_file_port_descr ? 1 : 0;

    if (audio_file_node_count >= 1) {

        if (ag->audio_file_node) {
            genesis_node_destroy(ag->audio_file_node);
            ag->audio_file_node = nullptr;
        }

        if (ag->preview_audio_file) {
            // Set channel layout
            const struct SoundIoChannelLayout *channel_layout =
                genesis_audio_file_channel_layout(ag->preview_audio_file);
            genesis_audio_port_descriptor_set_channel_layout(
                    ag->audio_file_port_descr, channel_layout, true, -1);

            // Set sample rate
            int sample_rate = genesis_audio_file_sample_rate(ag->preview_audio_file);
            genesis_audio_port_descriptor_set_sample_rate(ag->audio_file_port_descr, sample_rate, true, -1);

        } else {
            genesis_audio_port_descriptor_set_channel_layout(
                    ag->audio_file_port_descr, target_channel_layout, true, -1);
            genesis_audio_port_descriptor_set_sample_rate(
                    ag->audio_file_port_descr, target_sample_rate, true, -1);
        }
        ag->audio_file_node = ok_mem(genesis_node_descriptor_create_node(ag->audio_file_descr));
    }


    int resample_audio_out_index = genesis_node_descriptor_find_port_index(ag->resample_descr, "audio_out");
    assert(resample_audio_out_index >= 0);

    // one for each of the audio clips and one for the sample file preview node
    int mix_port_count = audio_file_node_count + ag->audio_clip_list.length();

    ok_or_panic(create_mixer_descriptor(ag->pipeline, mix_port_count, &ag->mixer_descr));
    ag->mixer_node = ok_mem(genesis_node_descriptor_create_node(ag->mixer_descr));

    ok_or_panic(genesis_connect_audio_nodes(ag->mixer_node, ag->master_node));

    // We start on mixer port index 1 because index 0 is the audio out. Index 1 is
    // the first audio in.
    int next_mixer_port = 1;
    if (audio_file_node_count >= 1) {
        int audio_out_port_index = genesis_node_descriptor_find_port_index(ag->audio_file_descr, "audio_out");
        if (audio_out_port_index < 0)
            panic("port not found");

        GenesisPort *audio_out_port = genesis_node_port(ag->audio_file_node, audio_out_port_index);
        GenesisPort *audio_in_port = genesis_node_port(ag->mixer_node, next_mixer_port++);

        if ((err = genesis_connect_ports(audio_out_port, audio_in_port))) {
            if (err == GenesisErrorIncompatibleChannelLayouts ||
                err == GenesisErrorIncompatibleSampleRates)
            {
                ag->resample_node = ok_mem(genesis_node_descriptor_create_node(ag->resample_descr));
                ok_or_panic(genesis_connect_audio_nodes(ag->audio_file_node, ag->resample_node));

                GenesisPort *audio_out_port = genesis_node_port(ag->resample_node, resample_audio_out_index);
                ok_or_panic(genesis_connect_ports(audio_out_port, audio_in_port));
            } else {
                ok_or_panic(err);
            }
        }
    }

    for (int i = 0; i < ag->audio_clip_list.length(); i += 1) {
        AudioGraphClip *clip = ag->audio_clip_list.at(i);

        int audio_out_port_index = genesis_node_descriptor_find_port_index(clip->node_descr, "audio_out");
        if (audio_out_port_index < 0)
            panic("port not found");

        GenesisPort *audio_out_port = genesis_node_port(clip->node, audio_out_port_index);
        GenesisPort *audio_in_port = genesis_node_port(ag->mixer_node, next_mixer_port++);

        if ((err = genesis_connect_ports(audio_out_port, audio_in_port))) {
            if (err == GenesisErrorIncompatibleChannelLayouts ||
                err == GenesisErrorIncompatibleSampleRates)
            {
                clip->resample_node = ok_mem(genesis_node_descriptor_create_node(ag->resample_descr));
                ok_or_panic(genesis_connect_audio_nodes(clip->node, clip->resample_node));

                GenesisPort *audio_out_port = genesis_node_port(clip->resample_node,
                        resample_audio_out_index);
                ok_or_panic(genesis_connect_ports(audio_out_port, audio_in_port));
            } else {
                ok_or_panic(err);
            }
        }

        GenesisPort *events_in_port = genesis_node_port(clip->node, 1);
        GenesisPort *events_out_port = genesis_node_port(clip->event_node, 0);

        ok_or_panic(genesis_connect_ports(events_out_port, events_in_port));
    }


    fprintf(stderr, "\nStarting pipeline...\n");
    genesis_debug_print_pipeline(ag->pipeline);

    double start_time = ag->play_head_pos;

    assert(next_mixer_port == mix_port_count + 1);
    if ((err = genesis_pipeline_start(ag->pipeline, start_time)))
        panic("unable to start pipeline: %s", genesis_strerror(err));
}