static void on_page_switched (GtkNotebook * notebook, GParamSpec * arg, gpointer user_data) { BtMainPages *self = BT_MAIN_PAGES (user_data); BtSong *song; GHashTable *properties; GtkWidget *page; gchar *prop; guint page_num; // get objects g_object_get (self->priv->app, "song", &song, NULL); if (!song) { return; } g_object_get (notebook, "page", &page_num, NULL); GST_INFO ("page has switched : self=%p, page=%d", self, page_num); // ensure the new page gets focused, this sounds like a hack though page = gtk_notebook_get_nth_page (notebook, page_num); GTK_WIDGET_GET_CLASS (page)->focus (page, GTK_DIR_TAB_FORWARD); // remember page bt_child_proxy_get (song, "setup::properties", &properties, NULL); prop = g_strdup_printf ("%u", page_num); g_hash_table_insert (properties, g_strdup ("active-page"), prop); // release the reference g_object_unref (song); GST_INFO ("page-switched done"); }
static xmlNodePtr bt_song_info_persistence_save (const BtPersistence * const persistence, xmlNodePtr const parent_node) { const BtSongInfo *const self = BT_SONG_INFO (persistence); xmlNodePtr node = NULL; GST_DEBUG ("PERSISTENCE::song-info"); if ((node = xmlNewChild (parent_node, NULL, XML_CHAR_PTR ("meta"), NULL))) { if (!strcmp (self->priv->name, DEFAULT_SONG_NAME)) { gchar *file_path = NULL, *file_name, *ext; bt_child_proxy_get (self->priv->song, "song-io::file-name", &file_path, NULL); if (file_path) { file_name = g_path_get_basename (file_path); if ((ext = strrchr (file_name, '.'))) { *ext = '\0'; } GST_INFO ("using '%s' instead of default title", file_name); g_object_set ((gpointer) self, "name", file_name, NULL); g_free (file_name); g_free (file_path); } } if (self->priv->info) { xmlNewChild (node, NULL, XML_CHAR_PTR ("info"), XML_CHAR_PTR (self->priv->info)); } if (self->priv->name) { xmlNewChild (node, NULL, XML_CHAR_PTR ("name"), XML_CHAR_PTR (self->priv->name)); } if (self->priv->genre) { xmlNewChild (node, NULL, XML_CHAR_PTR ("genre"), XML_CHAR_PTR (self->priv->genre)); } if (self->priv->author) { xmlNewChild (node, NULL, XML_CHAR_PTR ("author"), XML_CHAR_PTR (self->priv->author)); } if (self->priv->create_dts) { xmlNewChild (node, NULL, XML_CHAR_PTR ("create-dts"), XML_CHAR_PTR (self->priv->create_dts)); } if (self->priv->change_dts) { xmlNewChild (node, NULL, XML_CHAR_PTR ("change-dts"), XML_CHAR_PTR (self->priv->change_dts)); } xmlNewChild (node, NULL, XML_CHAR_PTR ("bpm"), XML_CHAR_PTR (bt_str_format_ulong (self->priv->beats_per_minute))); xmlNewChild (node, NULL, XML_CHAR_PTR ("tpb"), XML_CHAR_PTR (bt_str_format_ulong (self->priv->ticks_per_beat))); xmlNewChild (node, NULL, XML_CHAR_PTR ("bars"), XML_CHAR_PTR (bt_str_format_ulong (self->priv->bars))); } return node; }
static void on_song_changed (const BtEditApplication * app, GParamSpec * arg, gpointer user_data) { BtMainPages *self = BT_MAIN_PAGES (user_data); BtSong *song; GHashTable *properties; gchar *prop; GST_INFO ("song has changed : app=%p, self=%p", app, self); // get song from app g_object_get (self->priv->app, "song", &song, NULL); if (!song) { return; } // restore page bt_child_proxy_get (song, "setup::properties", &properties, NULL); if ((prop = (gchar *) g_hash_table_lookup (properties, "active-page"))) { guint page = atoi (prop); gtk_notebook_set_current_page (GTK_NOTEBOOK (self), page); GST_INFO ("reactivate page %d", page); } // release the reference g_object_unref (song); GST_INFO ("song has changed done"); }
static void on_song_play_pos_notify (BtSong * song, GParamSpec * arg, gpointer user_data) { gulong pos, length; bt_child_proxy_get ((gpointer) song, "sequence::length", &length, "play-pos", &pos, NULL); GST_DEBUG ("%ld < %lu < %lu", old_pos, pos, length); if ((pos >= length) || (pos < old_pos)) { GST_INFO ("we reached the end"); } old_pos = pos; }
static void test_bt_child_proxy_get (BT_TEST_ARGS) { BT_TEST_START; GST_INFO ("-- arrange --"); gulong length; GST_INFO ("-- act --"); bt_child_proxy_get (song, "sequence::length", &length, NULL); GST_INFO ("-- assert --"); ck_assert_int_gt (length, 0); GST_INFO ("-- cleanup --"); BT_TEST_END; }
static void test_bt_child_proxy_set (BT_TEST_ARGS) { BT_TEST_START; GST_INFO ("-- arrange --"); gboolean loop; GST_INFO ("-- act --"); bt_child_proxy_set (song, "sequence::loop", TRUE, NULL); GST_INFO ("-- assert --"); bt_child_proxy_get (song, "sequence::loop", &loop, NULL); ck_assert_int_eq (TRUE, loop); GST_INFO ("-- cleanup --"); BT_TEST_END; }
static void test_bt_child_proxy_set_property (BT_TEST_ARGS) { BT_TEST_START; GST_INFO ("-- arrange --"); gboolean loop; GValue value = { 0, }; g_value_init (&value, G_TYPE_BOOLEAN); g_value_set_boolean (&value, TRUE); GST_INFO ("-- act --"); bt_child_proxy_set_property ((GObject *) song, "sequence::loop", &value); GST_INFO ("-- assert --"); bt_child_proxy_get (song, "sequence::loop", &loop, NULL); ck_assert_int_eq (TRUE, loop); GST_INFO ("-- cleanup --"); g_value_unset (&value); BT_TEST_END; }
/** * bt_edit_application_save_song: * @self: the application instance to save a song from * @file_name: the song filename to save * @err: where to store the error message in case of an error, or %NULL * * Saves a song. * * Returns: true for success */ gboolean bt_edit_application_save_song (const BtEditApplication * self, const char *file_name, GError ** err) { gboolean res = FALSE; BtSongIO *saver; g_return_val_if_fail (BT_IS_EDIT_APPLICATION (self), FALSE); GST_INFO ("song name = %s", file_name); if ((saver = bt_song_io_from_file (file_name, err))) { gchar *old_file_name = NULL, *bak_file_name = NULL; bt_edit_application_ui_lock (self); g_signal_connect (saver, "notify::status", G_CALLBACK (on_songio_status_changed), (gpointer) self); // update the time-stamp bt_child_proxy_set (self->priv->song, "song-info::change-dts", NULL, NULL); bt_child_proxy_get (self->priv->song, "song-info::file-name", &old_file_name, NULL); /* save file saving (bak files) * save * new file (!old_file_name) * chosen file-name already exist * - move to <existing>.bak * - save newfile * - if saving failed, move <existing>.bak back * - if saving worked, delete <existing>.bak * chosen file-name does not exist * - save newfile * existing file * - move to <existing>.bak * - save newfile * - if saving failed, move <existing>.bak back * save-as * new file (!old_file_name) * like save of a new-file * existing file * chosen file-name already exist * - like save of an existing file * chosen file-name does not exist * - save newfile * * - check how other apps do it (check if inodes change if various scenarios) * - when loading a file, should we keep the file-handle open, so then when * saving, we can just update it? * - this can help with the wavetable (only updated changed wavetable slots) * - if we can't update it, we can use gsf_input_copy() for external files * (if unchanged) * - if the user deletes the file that is currently open -> user error */ if (g_file_test (file_name, G_FILE_TEST_EXISTS)) { bak_file_name = g_strconcat (file_name, ".bak", NULL); g_rename (file_name, bak_file_name); } if (bt_song_io_save (saver, self->priv->song, err)) { res = TRUE; if (!old_file_name || strcmp (old_file_name, file_name)) { // saving worked, we remove the bak file as // - there was no old_file_name and/or // - user has chosen to overwrite this file g_unlink (bak_file_name); } } else { GST_WARNING ("could not save song \"%s\"", file_name); if (bak_file_name) { // saving failed, so move a file we renamed to .bak back g_rename (bak_file_name, file_name); } } GST_INFO ("saving done"); self->priv->unsaved = FALSE; g_object_notify (G_OBJECT (self), "unsaved"); self->priv->need_dts_reset = TRUE; bt_edit_application_ui_unlock (self); g_free (old_file_name); g_free (bak_file_name); g_object_unref (saver); } else { GST_WARNING ("Unknown extension \"%s\"", file_name); } return res; }
/** * bt_edit_application_load_song: * @self: the application instance to load a new song in * @file_name: the song filename to load * @err: where to store the error message in case of an error, or %NULL * * Loads a new song. If there is a previous song instance it will be freed. * * Returns: true for success */ gboolean bt_edit_application_load_song (const BtEditApplication * self, const char *file_name, GError ** err) { gboolean res = FALSE; BtSongIO *loader; BtSong *song; g_return_val_if_fail (BT_IS_EDIT_APPLICATION (self), FALSE); GST_INFO ("song name = %s", file_name); if ((loader = bt_song_io_from_file (file_name, err))) { BtSetup *setup; GList *missing_machines, *missing_waves; bt_edit_application_ui_lock (self); g_signal_connect (loader, "notify::status", G_CALLBACK (on_songio_status_changed), (gpointer) self); // create new song and release the previous one song = bt_song_new (BT_APPLICATION (self)); g_object_set ((gpointer) self, "song", NULL, NULL); #ifdef USE_DEBUG // do sanity check that bin is empty { GstBin *bin; g_object_get ((gpointer) self, "bin", &bin, NULL); if (GST_BIN_NUMCHILDREN (bin)) { GList *node = GST_BIN_CHILDREN (bin); GST_WARNING ("bin.num_children=%d has left-overs", GST_BIN_NUMCHILDREN (bin)); while (node) { GST_WARNING_OBJECT (node->data, "removing object %" G_OBJECT_REF_COUNT_FMT, G_OBJECT_LOG_REF_COUNT (node->data)); gst_bin_remove (bin, GST_ELEMENT (node->data)); node = GST_BIN_CHILDREN (bin); } } gst_object_unref (bin); } #endif // this is synchronous execution // https://github.com/Buzztrax/buzztrax/issues/52 // if we bump glib from 2.32 -> 2.36 we can use GTask and // g_task_run_in_thread() if (bt_song_io_load (loader, song, err)) { BtMachine *machine; // get sink-machine g_object_get (song, "setup", &setup, NULL); if ((machine = bt_setup_get_machine_by_type (setup, BT_TYPE_SINK_MACHINE))) { if (bt_machine_enable_input_post_level (machine)) { // DEBUG //bt_song_write_to_highlevel_dot_file(song); // DEBUG // set new song g_object_set ((gpointer) self, "song", song, NULL); res = TRUE; GST_INFO ("new song activated"); } else { GST_WARNING ("Can't add input level/gain element in sink machine"); } GST_DEBUG ("unreffing stuff after loading"); g_object_unref (machine); } else { GST_WARNING ("Can't look up sink machine"); } g_object_unref (setup); } else { GST_WARNING ("could not load song \"%s\"", file_name); } self->priv->unsaved = FALSE; g_object_notify (G_OBJECT (self), "unsaved"); bt_edit_application_ui_unlock (self); // get missing element info bt_child_proxy_get (song, "setup::missing-machines", &missing_machines, "wavetable::missing-waves", &missing_waves, NULL); // tell about missing machines and/or missing waves if (missing_machines || missing_waves) { GtkWidget *dialog; if ((dialog = GTK_WIDGET (bt_missing_song_elements_dialog_new (missing_machines, missing_waves)))) { bt_edit_application_attach_child_window (self, GTK_WINDOW (dialog)); gtk_widget_show_all (dialog); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); } } g_object_unref (song); g_object_unref (loader); } else { GST_WARNING ("Unknown extension \"%s\"", file_name); } return res; }
/** * bt_edit_application_new_song: * @self: the application instance to create a new song in * * Creates a new blank song instance. If there is a previous song instance it * will be freed. * * Returns: %TRUE for success */ gboolean bt_edit_application_new_song (const BtEditApplication * self) { gboolean res = FALSE; BtSong *song; BtSetup *setup; BtMachine *machine; gchar *id; gulong bars; GError *err = NULL; g_return_val_if_fail (BT_IS_EDIT_APPLICATION (self), FALSE); // create new song song = bt_song_new (BT_APPLICATION (self)); bt_child_proxy_get (song, "setup", &setup, "song-info::bars", &bars, NULL); // make initial song length 4 timelines bt_child_proxy_set (song, "sequence::length", bars * 4, NULL); // add audiosink id = bt_setup_get_unique_machine_id (setup, "master"); machine = BT_MACHINE (bt_sink_machine_new (song, id, &err)); if (err == NULL) { GHashTable *properties; GST_DEBUG ("sink-machine=%" G_OBJECT_REF_COUNT_FMT, G_OBJECT_LOG_REF_COUNT (machine)); g_object_get (machine, "properties", &properties, NULL); if (properties) { gchar str[G_ASCII_DTOSTR_BUF_SIZE]; g_hash_table_insert (properties, g_strdup ("xpos"), g_strdup (g_ascii_dtostr (str, G_ASCII_DTOSTR_BUF_SIZE, 0.0))); g_hash_table_insert (properties, g_strdup ("ypos"), g_strdup (g_ascii_dtostr (str, G_ASCII_DTOSTR_BUF_SIZE, 0.0))); } if (bt_machine_enable_input_post_level (machine)) { GST_DEBUG ("sink-machine=%" G_OBJECT_REF_COUNT_FMT, G_OBJECT_LOG_REF_COUNT (machine)); // set new song in application g_object_set ((gpointer) self, "song", song, NULL); res = TRUE; } else { GST_WARNING ("Can't add input level/gain element in sink machine"); } GST_DEBUG ("sink-machine=%" G_OBJECT_REF_COUNT_FMT, G_OBJECT_LOG_REF_COUNT (machine)); } else { GST_WARNING ("Can't create sink machine: %s", err->message); g_error_free (err); gst_object_unref (machine); } g_free (id); self->priv->unsaved = FALSE; g_object_notify (G_OBJECT (self), "unsaved"); // release references g_object_unref (setup); g_object_unref (song); return res; }