static void add_event_node_to_audio_clip(AudioGraph *ag, AudioGraphClip *clip) { assert(!clip->event_node_descr); assert(!clip->event_node); const char *name = "audio_clip_events"; char *description = create_formatted_str("Events for Audio Clip: %s", clip->audio_clip->name.encode().raw()); GenesisNodeDescriptor *node_descr = ok_mem(genesis_create_node_descriptor(ag->pipeline, 1, name, description)); free(description); description = nullptr; genesis_node_descriptor_set_userdata(node_descr, clip); struct GenesisPortDescriptor *events_out_port = genesis_node_descriptor_create_port( node_descr, 0, GenesisPortTypeEventsOut, "events_out"); if (!events_out_port) panic("unable to create ports"); genesis_node_descriptor_set_run_callback(node_descr, audio_clip_event_node_run); genesis_node_descriptor_set_seek_callback(node_descr, audio_clip_event_node_seek); genesis_node_descriptor_set_create_callback(node_descr, audio_clip_event_node_create); genesis_node_descriptor_set_destroy_callback(node_descr, audio_clip_event_node_destroy); clip->event_node_descr = node_descr; clip->event_node = ok_mem(genesis_node_descriptor_create_node(node_descr)); }
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; }
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"); } } }
static int init_playback_node(AudioGraph *ag) { MixerLine *master_mixer_line = ag->project->mixer_line_list.at(0); Effect *first_effect = master_mixer_line->effects.at(0); assert(first_effect->effect_type == EffectTypeSend); EffectSend *effect_send = &first_effect->effect.send; assert(effect_send->send_type == EffectSendTypeDevice); EffectSendDevice *send_device = &effect_send->send.device; SoundIoDevice *audio_device = get_device_for_id(ag, (DeviceId)send_device->device_id); if (!audio_device) { return GenesisErrorDeviceNotFound; } GenesisNodeDescriptor *playback_node_descr; int err; if ((err = genesis_audio_device_create_node_descriptor(ag->pipeline, audio_device, &playback_node_descr))) { return err; } assert(!ag->master_node); ag->master_node = ok_mem(genesis_node_descriptor_create_node(playback_node_descr)); soundio_device_unref(audio_device); return 0; }
PngImage::PngImage(const ByteBuffer &compressed_bytes) { if (png_sig_cmp((png_bytep)compressed_bytes.raw(), 0, 8)) panic("not png file"); png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) panic("unable to create png read struct"); png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) panic("unable to create png info struct"); // don't call any png_* functions outside of this function D: if (setjmp(png_jmpbuf(png_ptr))) panic("libpng has jumped the shark"); png_set_sig_bytes(png_ptr, 8); PngIo png_io = {8, (unsigned char *)compressed_bytes.raw(), compressed_bytes.length()}; png_set_read_fn(png_ptr, &png_io, read_png_data); png_read_info(png_ptr, info_ptr); _width = png_get_image_width(png_ptr, info_ptr); _height = png_get_image_height(png_ptr, info_ptr); if (_width <= 0 || _height <= 0) panic("spritesheet image has no pixels"); // bits per channel (not per pixel) int bits_per_channel = png_get_bit_depth(png_ptr, info_ptr); if (bits_per_channel != 8) panic("expected 8 bits per channel"); int channel_count = png_get_channels(png_ptr, info_ptr); if (channel_count != 4) panic("expected 4 channels"); int color_type = png_get_color_type(png_ptr, info_ptr); if (color_type != PNG_COLOR_TYPE_RGBA) panic("expected RGBA"); _pitch = _width * bits_per_channel * channel_count / 8; _image_data.resize(_height * _pitch); png_bytep *row_ptrs = ok_mem(allocate_zero<png_bytep>(_height)); for (int i = 0; i < _height; i++) { png_uint_32 q = (_height - i - 1) * _pitch; row_ptrs[i] = (png_bytep)_image_data.raw() + q; } png_read_image(png_ptr, row_ptrs); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); destroy(row_ptrs, _height); }
Gui::Gui(GenesisContext *context, ResourceBundle *resource_bundle) : _running(true), _focus_window(nullptr), _utility_window(create_utility_window()), _resource_bundle(resource_bundle), _spritesheet(this, "spritesheet"), img_entry_dir(_spritesheet.get_image_info("font-awesome/folder.png")), img_entry_dir_open(_spritesheet.get_image_info("font-awesome/folder-open.png")), img_entry_file(_spritesheet.get_image_info("font-awesome/file.png")), img_plus(_spritesheet.get_image_info("font-awesome/plus-square.png")), img_minus(_spritesheet.get_image_info("font-awesome/minus-square.png")), img_microphone(_spritesheet.get_image_info("font-awesome/microphone.png")), img_volume_up(_spritesheet.get_image_info("font-awesome/volume-up.png")), img_check(_spritesheet.get_image_info("font-awesome/check.png")), img_caret_right(_spritesheet.get_image_info("font-awesome/caret-right.png")), img_caret_down(_spritesheet.get_image_info("font-awesome/caret-down.png")), img_arrow_up(_spritesheet.get_image_info("font-awesome/arrow-up.png")), img_arrow_down(_spritesheet.get_image_info("font-awesome/arrow-down.png")), img_arrow_left(_spritesheet.get_image_info("font-awesome/arrow-left.png")), img_arrow_right(_spritesheet.get_image_info("font-awesome/arrow-right.png")), img_music(_spritesheet.get_image_info("font-awesome/music.png")), img_plug(_spritesheet.get_image_info("font-awesome/plug.png")), img_exclamation_circle(_spritesheet.get_image_info("font-awesome/exclamation-circle.png")), img_null(_spritesheet.get_image_info("img/null.png")), img_play_head(_spritesheet.get_image_info("img/play_head.png")), _genesis_context(context), dragging(false), drag_data(nullptr), drag_window(nullptr) { ft_ok(FT_Init_FreeType(&_ft_library)); _resource_bundle->get_file_buffer("font.ttf", _default_font_buffer); ft_ok(FT_New_Memory_Face(_ft_library, (FT_Byte*)_default_font_buffer.raw(), _default_font_buffer.length(), 0, &_default_font_face)); cursor_default = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); cursor_ibeam = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); cursor_hresize = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); cursor_vresize = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); genesis_set_audio_device_callback(_genesis_context, audio_device_callback, this); genesis_set_midi_device_callback(_genesis_context, midi_device_callback, this); genesis_set_sound_backend_disconnect_callback(_genesis_context, sound_backend_disconnect_callback, this); genesis_flush_events(_genesis_context); genesis_refresh_midi_devices(_genesis_context); gui_mutex = ok_mem(os_mutex_create()); os_mutex_lock(gui_mutex); }
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)); }
void os_spawn_process(const char *exe, const List<ByteBuffer> &args, bool detached) { pid_t pid = fork(); if (pid == -1) panic("fork failed"); if (pid != 0) return; if (detached) { if (setsid() == -1) panic("process detach failed"); } const char **argv = ok_mem(allocate_zero<const char *>(args.length() + 2)); argv[0] = exe; argv[args.length() + 1] = nullptr; for (int i = 0; i < args.length(); i += 1) { argv[i + 1] = args.at(i).raw(); } execvp(exe, const_cast<char * const *>(argv)); panic("execvp failed: %s", strerror(errno)); }
int audio_graph_create_render(Project *project, GenesisContext *genesis_context, const GenesisExportFormat *export_format, const ByteBuffer &out_path, AudioGraph **out_audio_graph) { AudioGraph *ag = audio_graph_create_common(project, genesis_context, 0.10); ag->render_export_format = *export_format; ag->render_out_path = out_path; ag->render_frame_index = 0; ag->render_frame_count = project_get_duration_frames(project); ag->render_cond = ok_mem(os_cond_create()); ag->is_playing = true; ag->render_descr = genesis_create_node_descriptor(ag->pipeline, 1, "render", "Master render node."); if (!ag->render_descr) panic("unable to create render node descriptor"); genesis_node_descriptor_set_userdata(ag->render_descr, ag); genesis_node_descriptor_set_run_callback(ag->render_descr, render_node_run); genesis_node_descriptor_set_activate_callback(ag->render_descr, render_node_activate); ag->render_port_descr = genesis_node_descriptor_create_port( ag->render_descr, 0, GenesisPortTypeAudioIn, "audio_in"); if (!ag->render_port_descr) panic("unable to create render port descriptor"); genesis_audio_port_descriptor_set_channel_layout(ag->render_port_descr, &project->channel_layout, true, -1); genesis_audio_port_descriptor_set_sample_rate(ag->render_port_descr, export_format->sample_rate, true, -1); ag->master_node = ok_mem(genesis_node_descriptor_create_node(ag->render_descr)); ag->render_stream = ok_mem(genesis_audio_file_stream_create(ag->pipeline->context)); int render_sample_rate = genesis_audio_file_codec_best_sample_rate(export_format->codec, export_format->sample_rate); genesis_audio_file_stream_set_sample_rate(ag->render_stream, render_sample_rate); genesis_audio_file_stream_set_channel_layout(ag->render_stream, &project->channel_layout); ByteBuffer encoded; encoded = project->tag_title.encode(); genesis_audio_file_stream_set_tag(ag->render_stream, "title", -1, encoded.raw(), encoded.length()); encoded = project->tag_artist.encode(); genesis_audio_file_stream_set_tag(ag->render_stream, "artist", -1, encoded.raw(), encoded.length()); encoded = project->tag_album_artist.encode(); genesis_audio_file_stream_set_tag(ag->render_stream, "album_artist", -1, encoded.raw(), encoded.length()); encoded = project->tag_album.encode(); genesis_audio_file_stream_set_tag(ag->render_stream, "album", -1, encoded.raw(), encoded.length()); // TODO looks like I messed up the year tag; it should actually be ISO 8601 "date" // TODO so we need to write the date tag here, not year. genesis_audio_file_stream_set_export_format(ag->render_stream, export_format); int err; if ((err = genesis_audio_file_stream_open(ag->render_stream, out_path.raw(), out_path.length()))) { audio_graph_destroy(ag); return err; } *out_audio_graph = ag; 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)); }
SettingsFile *settings_file_open(const ByteBuffer &path) { SettingsFile *sf = ok_mem(create_zero<SettingsFile>()); sf->path = path; // default settings sf->open_project_id = uint256::zero(); sf->user_name = ""; sf->user_id = uint256::zero(); FILE *f = fopen(path.raw(), "rb"); if (!f) { if (errno == ENOENT) { // no settings file, leave everything at default return sf; } panic("unable to open settings file: %s", strerror(errno)); } sf->state = SettingsFileStateStart; LaxJsonContext *json = ok_mem(lax_json_create()); sf->json = json; json->userdata = sf; json->string = on_string; json->number = on_number; json->primitive = on_primitive; json->begin = on_begin; json->end = on_end; struct stat st; if (fstat(fileno(f), &st)) panic("fstat failed"); ByteBuffer buf; buf.resize(st.st_size); int amt_read = fread(buf.raw(), 1, buf.length(), f); if (fclose(f)) panic("fclose error"); if (amt_read != buf.length()) panic("error reading settings file"); handle_parse_error(sf, lax_json_feed(json, amt_read, buf.raw())); handle_parse_error(sf, lax_json_eof(json)); for (int i = 0; i < sf->open_windows.length(); i += 1) { SettingsFileOpenWindow *open_window = &sf->open_windows.at(i); if (open_window->perspective_index < 0 || open_window->perspective_index >= sf->perspectives.length()) { panic("window %d perspective index out of bounds: %d", i + 1, open_window->perspective_index); } } sf->json = nullptr; lax_json_destroy(json); return sf; }
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; }