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; } }
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; }
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; }
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"); } } }
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(); }
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); }
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(); }
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(); }
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; }
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)); }
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; }
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; }
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); }
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; }
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; }
void clear() { _buffer.clear(); ok_or_panic(_buffer.append(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; }
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)); }