+/* Demo Recorder for MAEMO 5
+* Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2,
+* or (at your option) any later version, as published by the Free
+* Software Foundation
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details
+*
+* You should have received a copy of the GNU General Public
+* License along with this program; if not, write to the
+* Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+namespace IdWorks {
+
+public class DemoRecorder : Hildon.Program {
+
+ ApplicationSettings application_settings;
+ Hildon.StackableWindow window;
+ Gtk.VBox tracks;
+ SettingsStructures.ProjectSettings project;
+ bool project_dirty;
+ EncodePipeline encodepipeline;
+ PlayPipeline playpipeline;
+ RecordPipeline recordpipeline;
+ PlayerTransport player;
+ MixerBin mixer;
+ uint track_counter;
+ bool recording;
+ bool playing;
+ bool encoding;
+ uint screen_timer_id;
+ uint playback_timer_id;
+ uint recording_timer_id;
+ uint encoding_timer_id;
+ ulong recording_position_duration_id;
+ Osso.Context osso_context;
+ //int64 track_position;
+ //int64 record_position;
+ //int64 track_duration;
+
+ public signal void encoding_starting();
+ public signal void encoding_started();
+ public signal void encoding_ending();
+ public signal void encoding_ended();
+ public signal void recording_starting();
+ public signal void recording_started();
+ public signal void recording_ending();
+ public signal void recording_ended();
+ public signal void playback_starting();
+ public signal void playback_started();
+ public signal void playback_ending();
+ public signal void playback_ended();
+ public signal void playback_pausing();
+ public signal void playback_paused();
+ public signal void playback_resuming();
+ public signal void playback_resumed();
+ public signal void playback_position_duration(int64 position, int64 duration);
+ public signal void recording_position_duration(int64 position, int64 duration);
+ public signal void encoding_position_duration(int64 position, int64 duration);
+
+ public void playpipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
+ playback_position_duration(position, duration);
+ if (position >= duration) playback_time_elapsed();
+ }
+ public void recordpipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
+ recording_position_duration(position, duration);
+ if (0 < duration && position >= duration) recording_time_elapsed();
+ }
+ public void encodepipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
+ encoding_position_duration(position, duration);
+ if (position >= duration) encoding_time_elapsed();
+ }
+ private bool update_time_and_duration_play_pipeline() {
+ playpipeline.get_duration_info();
+ return true;
+ }
+ private bool update_time_and_duration_record_pipeline() {
+ recordpipeline.get_duration_info();
+ return true;
+ }
+ private bool update_time_and_duration_encode_pipeline() {
+ encodepipeline.get_duration_info();
+ return true;
+ }
+ private void recording_time_elapsed() {
+ Source.remove(recording_timer_id);
+ /*
+ this should only ever fire for fixed time 'recording'
+ i.e. bouncing or mixing tracks
+ hence we can use it as a trigger
+ */
+ }
+ private void encoding_time_elapsed() {
+ Source.remove(encoding_timer_id);
+ /*
+ this should only ever fire for fixed time 'recording'
+ i.e. bouncing or mixing tracks
+ hence we can use it as a trigger
+ */
+ }
+ private void playback_time_elapsed() {
+ if (!this.recording) Source.remove(playback_timer_id);
+ }
+
+ construct {
+ osso_context = new Osso.Context("DemoRecorder", "0.1", false, null);
+ project = SettingsStructures.ProjectSettings();
+ recording = false;
+ playing = false;
+ track_counter = 0;
+ window = new Hildon.StackableWindow();
+ window.destroy.connect(Gtk.main_quit);
+ window.delete_event.connect(on_delete_event);
+ window.set_title("Demo Recorder");
+ construct_menu();
+ Gtk.VBox container = new Gtk.VBox(false, 0);
+ Hildon.PannableArea scrollwin = new Hildon.PannableArea();
+ tracks = new Gtk.VBox(false, 0);
+ player = new PlayerTransport();
+ player.window = this.window;
+ this.playback_position_duration.connect(player.position_duration_callback);
+ player.play_clicked.connect(mixer_play_all);
+ player.pause_toggled.connect(mixer_pause);
+ player.stop_clicked.connect(mixer_stop);
+ player.volume_updated.connect(mixer_volume_updated);
+ player.panorama_updated.connect(mixer_panorama_updated);
+ player.eq_updated.connect(mixer_eq_updated);
+ player.record_toggled.connect(mixer_record);
+ container.pack_start(player, false, false, 0);
+ scrollwin.add_with_viewport(tracks);
+ container.pack_end(scrollwin, true, true, 0);
+ window.add(container);
+ mixer = new MixerBin("mixer");
+ //playpipeline = new PlayPipeline("mixer");
+ }
+
+ private void mixer_volume_updated(PlayerTransport sender, double volume) {
+ mixer.set_volume(volume);
+ }
+
+ private void mixer_panorama_updated(PlayerTransport sender, double panorama) {
+ mixer.set_panorama(panorama);
+ }
+
+ private void mixer_eq_updated(PlayerTransport sender, int band, double val) {
+ mixer.set_eq(band, val);
+ }
+
+ private bool keep_screen_on() {
+ if (this.recording) {
+ Osso.Status status = this.osso_context.display_blanking_pause();
+ /*if (Osso.Status.OK == status) {
+ stdout.printf("%s\n", "OK");
+ }
+ else if (Osso.Status.INVALID == status) {
+ stdout.printf("%s\n", "INVALID");
+ }
+ else if (Osso.Status.ERROR == status) {
+ stdout.printf("%s\n", "ERROR");
+ }
+ else {
+ stdout.printf("%s\n", "UNKNOWN");
+ }
+ stdout.flush();*/
+ return true;
+ }
+ else {
+ Source.remove(screen_timer_id);
+ return false;
+ }
+ }
+
+ private void mixer_play_all() {
+ /// TODO clean up old mixer first?
+ mixer = new MixerBin("mixer");
+ int idx = 0;
+ if (0 < project.tracks.length()) {
+ this.playing = true;
+// for (int i = 0; i < tracks.children.length(); ++i) {
+// TrackTransport tt = tracks.children.nth_data(i) as TrackTransport;
+// if (null != tt) {
+// if (tt.get_active_state()) {
+// stdout.printf("Track %s active and has been added\n", i.to_string());
+// //track.bin.set_start_time((Gst.ClockTime)(Time.Nanoseconds.SECOND * idx));
+// mixer.add_track(tt.track_bin);
+// }
+// }
+// tt = null;
+// }
+ while (idx < project.tracks.length()) {
+ Track track = project.tracks.nth_data(idx);
+ if (track.active) {
+ stdout.printf("Track %s active and has been added\n", idx.to_string());
+ //track.bin.set_start_time((Gst.ClockTime)(Time.Nanoseconds.SECOND * idx));
+ mixer.add_track(track.bin);
+ }
+ ++idx;
+ }
+ playpipeline = new PlayPipeline("player", mixer);
+ this.playback_position_duration.connect(player.position_duration_callback);
+ playpipeline.position_duration.connect(playpipeline_position_duration_callback);
+ playback_starting();
+ playpipeline.play();
+ playback_started();
+ update_time_and_duration_play_pipeline();
+ if (0 != playback_timer_id) Source.remove(playback_timer_id);
+ playback_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND / 10, update_time_and_duration_play_pipeline);
+ }
+ if (this.recording) {
+ keep_screen_on();
+ if (0 != screen_timer_id) Source.remove(screen_timer_id);
+ screen_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND * 20, keep_screen_on);
+ this.playback_position_duration.disconnect(player.position_duration_callback);
+ this.recording_position_duration.connect(player.position_duration_callback);
+ recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
+ recording_starting();
+ recordpipeline.record();
+ recording_started();
+ update_time_and_duration_record_pipeline();
+ if (0 != recording_timer_id) Source.remove(recording_timer_id);
+ recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND / 10, update_time_and_duration_record_pipeline);
+ }
+ }
+
+ private void mixer_play() {
+ this.playing = true;
+ playback_resuming();
+ playpipeline.play();
+ playback_resumed();
+ }
+
+ private void mixer_pause(bool val) {
+ if (val) {
+ playback_pausing();
+ playpipeline.pause();
+ playback_paused();
+ }
+ else {
+ playback_resuming();
+ playpipeline.play();
+ playback_resumed();
+ }
+ }
+
+ private void mixer_record(bool val) {
+ if (val) {
+ prepare_record();
+ }
+ else {
+ this.recording = false;
+ }
+ }
+
+ private void mixer_stop() {
+ if (this.recording) {
+ recording_ending();
+ this.recording = false;
+ recordpipeline.stop();
+ //this.import_track_from_uri(Filename.to_uri(recordpipeline.get_location()));
+ Track track = Track.with_uri(Filename.to_uri(recordpipeline.get_location()), "Track-" + (project.last_track_number).to_string(), true, true, 1.0, 0.0);
+ project.tracks.append(track);
+ add_track_transport(track);
+ recordpipeline = null;
+ recording_ended();
+ }
+ if (this.playing) {
+ playback_ending();
+ playpipeline.stop();
+ playpipeline = null;
+ this.playing = false;
+ playback_ended();
+ }
+ //stdout.flush();
+ }
+
+ private void construct_menu() {
+ Hildon.AppMenu menu = new Hildon.AppMenu();
+ var btnNew = new Gtk.Button.with_label("New project");
+ btnNew.clicked.connect(new_project);
+ menu.append(btnNew);
+ var btnOpen = new Gtk.Button.with_label("Open project");
+ btnOpen.clicked.connect(open_project);
+ menu.append(btnOpen);
+ var btnSave = new Gtk.Button.with_label("Save project");
+ btnSave.clicked.connect(save_project);
+ menu.append(btnSave);
+ var btnImport = new Gtk.Button.with_label("Import track");
+ btnImport.clicked.connect(open_import);
+ menu.append(btnImport);
+ var btnStartACM = new Gtk.Button.with_label("Input settings");
+ btnStartACM.clicked.connect(start_acm);
+ menu.append(btnStartACM);
+ var btnBounce = new Gtk.Button.with_label("Bounce tracks");
+ btnBounce.clicked.connect(prepare_bounce);
+ menu.append(btnBounce);
+ var btnMixdown = new Gtk.Button.with_label("Mixdown");
+ btnMixdown.clicked.connect(prepare_mixdown);
+ menu.append(btnMixdown);
+ var btnSettings = new Gtk.Button.with_label("Settings");
+ btnSettings.clicked.connect(show_settings);
+ menu.append(btnSettings);
+ menu.show_all();
+ window.set_app_menu(menu);
+ }
+ private void show_settings() {
+ File settings = File.new_for_path(Environment.get_home_dir() + "/.demorecorder/settings.xml");
+ string errors = "";
+ FirstRunWizard dlg = new FirstRunWizard(window, "Demo Recorder");
+ dlg.set_transient_for(window);
+ dlg.app_settings = application_settings;
+ dlg.run();
+ application_settings = dlg.app_settings;
+ bool saved = ApplicationSettings.save_settings(settings.get_path(), application_settings, ref errors);
+ dlg.destroy();
+ dlg = null;
+ }
+ private void new_project() {
+ bool good = false;
+ // is the current project dirty?
+ if (project_dirty) {
+ // run an are you sure dialog
+ // Do you want to save your current project first? Yes, No, Cancel
+ YesNoCancelDialog alert = new YesNoCancelDialog(window, "Save changes to current project?", "You have unsaved changes in your current project.\nWould you like to save them first?", Gtk.ResponseType.CANCEL);
+ alert.set_transient_for(window);
+ int ret = alert.run();
+ alert.destroy();
+ alert = null;
+ // yes - save_project()
+ if (Gtk.ResponseType.YES == ret) {
+ save_project();
+ }
+ // cancel - return and do nothing
+ else if (Gtk.ResponseType.CANCEL == ret || Gtk.ResponseType.NO != ret) {
+ return;
+ }
+ // carry on and create_project()
+ }
+ ProjectSettingsDialog dlg = new ProjectSettingsDialog.with_values(window, "New project settings", "New project", "", "", application_settings.working_directory);
+ dlg.set_transient_for(window);
+ int ret = dlg.run();
+ if (Gtk.ResponseType.OK == ret) {
+ good = true;
+ this.project = SettingsStructures.ProjectSettings();
+ this.project.name = dlg.get_project_name();
+ this.project.working_directory = dlg.get_project_directory();
+ // ensure that the directories exist.
+ DirUtils.create_with_parents(this.project.working_directory + "/tracks", 0755);
+ DirUtils.create_with_parents(this.project.working_directory + "/mixes", 0755);
+ DirUtils.create_with_parents(this.project.working_directory + "/tmp", 0755);
+ this.project.location = dlg.get_project_filename();
+ save_project();
+ }
+ dlg.destroy();
+ dlg = null;
+ if (good) {
+ // Now should clean up the display in preparation for working on this new project.
+ //load_project(this.project.location);
+ application_settings.last_project_name = project.name;
+ application_settings.last_project_location = project.location;
+ window.set_title(project.name);
+ remove_all_tracks();
+ }
+ }
+
+ private void open_project() {
+ // is the current project dirty?
+ if (project_dirty) {
+ // run an are you sure dialog
+ // Do you want to save your current project first? Yes, No, Cancel
+ YesNoCancelDialog alert = new YesNoCancelDialog(window, "Save changes to current project?", "You have unsaved changes in your current project.\nWould you like to save them first?", Gtk.ResponseType.CANCEL);
+ alert.set_transient_for(window);
+ int ret = alert.run();
+ alert.destroy();
+ alert = null;
+ // yes - save_project()
+ if (Gtk.ResponseType.YES == ret) {
+ save_project();
+ }
+ // cancel - return and do nothing
+ else if (Gtk.ResponseType.CANCEL == ret || Gtk.ResponseType.NO != ret) {
+ return;
+ }
+ // carry on and load_project()
+ }
+ // get a filename
+ Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.OPEN);
+ file_chooser.set_show_upnp(false);
+ file_chooser.set_current_folder(application_settings.working_directory);
+ int ret = file_chooser.run();
+ if (Gtk.ResponseType.OK == ret) {
+ load_project(file_chooser.get_filename());
+ }
+ file_chooser.destroy ();
+ file_chooser = null;
+ }
+
+ private void load_project(string location) {
+ remove_all_tracks();
+ project = SettingsStructures.ProjectSettings();
+ string errors = "";
+ if (XmlHelpers.parse_project_settings_file(location, "1.0", ref project, ref errors)) {
+ // need to tear down and build up the interface again.
+ // as a test let's save it for inspection
+ //save_project_settings(project.location);
+ /// TODO
+ application_settings.last_project_name = project.name;
+ application_settings.last_project_location = project.location;
+ if (ApplicationSettings.save_settings(Environment.get_home_dir() + "/.demorecorder/settings.xml", application_settings, ref errors)) {
+ window.set_title(project.name);
+ for (int idx = 0; idx < project.tracks.length(); ++idx) {
+ add_track_transport((Track)project.tracks.nth_data(idx));
+ }
+ }
+ else {
+ stdout.printf("%s\n", errors);
+ stdout.flush();
+ }
+ }
+ else {
+ Hildon.Note note = new Hildon.Note.information(window, "Project couldn't be opened!\n%s".printf(errors));
+ note.run();
+ note.destroy();
+ note = null;
+ }
+ }
+
+ private void remove_all_tracks() {
+ List<Gtk.Widget> children = tracks.get_children();
+ for (int idx = 0; idx < children.length(); ++idx) {
+ tracks.remove((Gtk.Widget)children.nth_data(idx));
+ }
+ for (int idx = ((int)project.tracks.length()) - 1; idx > -1; --idx) {
+ project.tracks.delete_link(project.tracks.nth(idx));
+ }
+ }
+ private void remove_track_interactive(TrackTransport tt) {
+ string message = "";
+ string ns = "";
+ switch(application_settings.remove_track_action) {
+ case ApplicationSettings.RemoveTrackAction.DELETE_FILE:
+ remove_track(tt);
+ message = "Track deleted.";
+ FileUtils.remove(Filename.from_uri(tt.track.uri, out ns));
+ break;
+ case ApplicationSettings.RemoveTrackAction.PRESERVE_FILE:
+ remove_track(tt);
+ message = "Track removed.";
+ break;
+ case ApplicationSettings.RemoveTrackAction.PROMPT:
+ YesNoCancelDialog dlg = new YesNoCancelDialog(window, "Remove Track", "Do you want to delete the source file permanently?", Gtk.ResponseType.CANCEL);
+ dlg.set_transient_for(window);
+ int ret = dlg.run();
+ dlg.destroy();
+ if (Gtk.ResponseType.YES == ret) {
+ remove_track(tt);
+ message = "Track deleted.";
+ FileUtils.remove(Filename.from_uri(tt.track.uri, out ns));
+ }
+ else if (Gtk.ResponseType.NO == ret) {
+ remove_track(tt);
+ message = "Track removed.";
+ }
+ break;
+ default:
+ break;
+ }
+ if ("" != message) {
+ Hildon.Note note = new Hildon.Note.information(window, "%s".printf(message));
+ note.run();
+ note.destroy();
+ note = null;
+ }
+ }
+ private void remove_track(TrackTransport tt) {
+ project.tracks.remove(tt.track);
+ tracks.remove(tt);
+ tt = null;
+ }
+
+ private void save_project() {
+ if (0 < project.location.length) {
+ save_project_settings(project.location);
+ }
+ else {
+ do_save_as_project();
+ }
+ //stdout.printf(project.to_xml_string());
+ }
+ private void do_save_as_project() {
+ // get a filename
+ Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.SAVE);
+ file_chooser.set_show_upnp(false);
+ int ret = file_chooser.run();
+ if (Gtk.ResponseType.OK == ret) {
+ project.location = file_chooser.get_filename();
+ save_project_settings(project.location);
+ }
+ file_chooser.destroy ();
+ file_chooser = null;
+ }
+ private void save_project_settings(string location) {
+ bool success = false;
+ Hildon.gtk_window_set_progress_indicator(window, (uint)true);
+ string contents = project.to_xml_string();
+ // should ask each ui element for it's updated settings to be on the safe side first.
+ /// TODO
+ if (0 < contents.length)
+ {
+ contents = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + contents;
+ if (FileUtils.set_contents(location, contents, contents.length)) {
+ project_dirty = false;
+ // alert user of success? no!
+ //Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Project saved.");
+ //((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
+ }
+ else
+ {
+ // alert user of failure to save file.
+ Hildon.Note alert = new Hildon.Note.information(window, "Project couldn't be saved!\nFailed to save the project settings file.");
+ alert.run();
+ alert.destroy();
+ alert = null;
+ }
+ }
+ else {
+ // alert user of failure to create serialize settings.
+ Hildon.Note alert = new Hildon.Note.information(window, "Project couldn't be saved!\nFailed to serialize the project settings.");
+ alert.run();
+ alert.destroy();
+ alert = null;
+ }
+ Hildon.gtk_window_set_progress_indicator(window, (uint)false);
+ }
+
+ private void start_acm() {
+ AcmPopUp acm = new AcmPopUp.with_settings("Input Settings", window, project.monitor);
+ acm.set_transient_for(window);
+ acm.play();
+ if (Gtk.ResponseType.OK == acm.run()) {
+ project.monitor = acm.get_settings_structure();
+ project_dirty = true;
+ }
+ acm.stop();
+ acm.destroy();
+ acm = null;
+ }
+
+ private void prepare_mixdown() {
+ MixdownPopUp dlg = new MixdownPopUp.with_settings("Mixdown your track", window, project.mixdown);
+ dlg.set_transient_for(window);
+ if (Gtk.ResponseType.OK == dlg.run()) {
+ project.mixdown = dlg.get_settings_structure();
+ project_dirty = true;
+ do_mixdown();
+ }
+ dlg.destroy();
+ dlg = null;
+ }
+
+ private void do_mixdown() {
+ Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
+ "rate", typeof (int), 48000,
+ "channels", typeof (int), 2,
+ "depth", typeof (int), 16);
+ mixer = new MixerBin("mixer");
+ int idx = 0;
+ if (0 < project.tracks.length()) {
+ while (idx < project.tracks.length()) {
+ Track track = project.tracks.nth_data(idx);
+ if (track.active) {
+ mixer.add_track(track.bin);
+ }
+ ++idx;
+ }
+ this.recording = false;
+ this.playing = false;
+ Gst.Element? encoder = EncoderFactory.get_encoder_bin("encoder", project.mixdown.encoder, project.mixdown.quality, "artist=%s,title=%s,album=%s".printf(project.mixdown.artist, project.mixdown.title, project.mixdown.album));
+ if (null != encoder) {
+ encodepipeline = new EncodePipeline("mixdown", project.mixdown.location, mixer, encoder, caps);
+ encodepipeline.position_duration.connect(encodepipeline_position_duration_callback);
+ Hildon.gtk_window_set_progress_indicator(window, (uint)true);
+ encoding_starting();
+ encodepipeline.encode();
+ encoding_started();
+ update_time_and_duration_encode_pipeline();
+ if (0 != recording_timer_id) Source.remove(encoding_timer_id);
+ encoding_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_encode_pipeline);
+ ProgressPopUp dlg = new ProgressPopUp("Mixing down", window);
+ dlg.set_message("Mixing down your track.\nThis may take some time depending on the duration of the audio.\n");
+ dlg.set_transient_for(window);
+ this.encoding_position_duration.connect((p,d) => {
+ double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND));
+ dlg.set_progress(percent);
+ dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
+ //if (p >= d) {
+ // dlg.close_me();
+ // this.mixer_stop();
+ //}
+ });
+ encodepipeline.end_of_stream.connect((e) => {encoding_ending();dlg.close_me();encoding_time_elapsed();this.mixer_stop();encoding_ended();});
+ if (Gtk.ResponseType.CANCEL == dlg.run()) {
+ // Should really stop the mixer and delete the files!
+ /// TODO
+ this.mixer_stop();
+ }
+ dlg.destroy();
+ dlg = null;
+ //encoder = null;
+ Hildon.gtk_window_set_progress_indicator(window, (uint)false);
+ Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Mixdown complete");
+ ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
+ }
+ else {
+ Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Mixdown failed! Encoder not available.");
+ ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
+ }
+ }
+ }
+
+ private void prepare_bounce() {
+ project.last_track_number++;
+ string new_location = "%s/tracks/bounce-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
+ string name = "bounce-" + project.last_track_number.to_string();
+ do_bounce(new_location);
+ }
+
+ private void do_bounce(string filename) {
+ Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
+ "rate", typeof (int), 48000,
+ "channels", typeof (int), 2,
+ "depth", typeof (int), 16);
+ //Gst.Element src = Gst.ElementFactory.make("pulsesrc", "input");
+ //src.set_property("device", "source.hw0");
+ mixer = new MixerBin("mixer");
+ int idx = 0;
+ if (0 < project.tracks.length()) {
+ while (idx < project.tracks.length()) {
+ Track track = project.tracks.nth_data(idx);
+ if (track.active) {
+ mixer.add_track(track.bin);
+ }
+ ++idx;
+ }
+ this.recording = true;
+ this.playing = false;
+ recordpipeline = new RecordPipeline("bouncer", filename, mixer, caps, "wavenc");
+ recordpipeline.bouncing = true;
+ recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
+ Hildon.gtk_window_set_progress_indicator(window, (uint)true);
+ recording_starting();
+ recordpipeline.record();
+ recording_started();
+ update_time_and_duration_record_pipeline();
+ if (0 != recording_timer_id) Source.remove(recording_timer_id);
+ recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_record_pipeline);
+ ProgressPopUp dlg = new ProgressPopUp("Bouncing tracks", window);
+ dlg.set_transient_for(window);
+ dlg.set_message("Bouncing your selected tracks.\nThis may take some time depending on the duration of the audio.\n");
+ this.recording_position_duration.connect((p,d) => {
+ double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND));
+ dlg.set_progress(percent);
+ dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
+ if (p >= d) {
+ dlg.close_me();
+ this.mixer_stop();
+ this.recording = false;
+ if (0 != recording_timer_id) Source.remove(recording_timer_id);
+ }
+ });
+ if (Gtk.ResponseType.CANCEL == dlg.run()) {
+ // Should really stop the mixer and delete the files!
+ /// TODO
+ this.recording = false;
+ this.mixer_stop();
+ }
+ dlg.destroy();
+ dlg = null;
+ Hildon.gtk_window_set_progress_indicator(window, (uint)false);
+ Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Bounced track added to mixer");
+ ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
+ }
+ }
+
+ public void prepare_record() {
+ project.last_track_number++;
+ string new_location = "%s/tracks/track-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
+ string name = "Track-" + project.last_track_number.to_string();
+ do_record(name, new_location);
+ }
+
+ public void do_record(string name, string filename) {
+ Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
+ "rate", typeof (int), 48000,
+ "channels", typeof (int), 2,
+ "depth", typeof (int), 16);
+ InputBin src = new InputBin.with_settings("input", project.monitor.input);
+ //Gst.Element src = Gst.ElementFactory.make("pulsesrc", "input");
+ //src.set_property("device", "source.hw0");
+ recordpipeline = new RecordPipeline("recorder", filename, src, caps, "wavenc");
+ recordpipeline.bouncing = false;
+ this.recording = true;
+ //recordpipeline.record();
+ }
+
+ public void open_import() {
+ Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.OPEN);
+ file_chooser.set_show_upnp(false);
+ file_chooser.set_current_folder(application_settings.working_directory);
+ if (file_chooser.run () == Gtk.ResponseType.OK) {
+ import_track_from_uri(file_chooser.get_uri());
+ }
+ file_chooser.destroy ();
+ }
+
+ public void import_track_from_uri(string uri) {
+ /// TODO - Should become import track from uri and convert to wav and name appropriately
+ ++project.last_track_number;
+ project_dirty = true;
+ Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
+ "rate", typeof (int), 48000,
+ "channels", typeof (int), 2,
+ "depth", typeof (int), 16);
+ /// TODO check that directories exist
+ string new_location = "%s/tracks/import-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
+ string name = "Import-" + project.last_track_number.to_string();
+ ImportBin import_bin = new ImportBin(name);
+ import_bin.set_uri(uri);
+
+ /// TODO encode to correct destination and add track
+ this.recording = false;
+ this.playing = false;
+ recordpipeline = new RecordPipeline("importer", new_location, import_bin, caps, "wavenc");
+ recordpipeline.bouncing = true;
+ recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
+ Hildon.gtk_window_set_progress_indicator(window, (uint)true);
+ recording_starting();
+ recordpipeline.record();
+ recording_started();
+ update_time_and_duration_record_pipeline();
+ if (0 != recording_timer_id) Source.remove(recording_timer_id);
+ recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_record_pipeline);
+ ProgressPopUp dlg = new ProgressPopUp("Importing track", window);
+ dlg.set_transient_for(window);
+ dlg.set_message("Importing your selected track.\nThis may take some time depending on the duration of the audio.\n");
+ this.recording_position_duration.connect((p,d) => {
+ double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND));
+ dlg.set_progress(percent);
+ dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
+ });
+ recordpipeline.end_of_stream.connect(() => {
+ dlg.close_me();
+ recordpipeline.stop();
+ if (0 != recording_timer_id) Source.remove(recording_timer_id);
+ Track track = Track.with_uri(Filename.to_uri(new_location), name, true, true, 1.0, 0.0);
+ project.tracks.append(track);
+ add_track_transport(track);
+ this.recording = false;
+ });
+ if (Gtk.ResponseType.CANCEL == dlg.run()) {
+ // Should really stop the mixer and delete the files!
+ /// TODO
+ //Track track = new Track.with_uri(Filename.to_uri(new_location), name, true, true, 1.0, 0.0);
+ //project.tracks.append(track);
+ //add_track_transport(track);
+ recordpipeline.stop();
+ this.recording = false;
+ }
+ dlg.destroy();
+ dlg = null;
+ Hildon.gtk_window_set_progress_indicator(window, (uint)false);
+ Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Imported track added to mixer");
+ ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
+ }
+
+ private void add_track_transport(Track track) {
+ TrackTransport trackt = new TrackTransport();
+ trackt.window = this.window;
+ trackt.track_bin = track.bin;
+ trackt.track = track;
+ trackt.title = track.label; //"Track %n".printf(project.track.length());
+ trackt.name = track.label;
+ trackt.delete_clicked.connect((tt) => {this.remove_track_interactive(tt);});
+ trackt.volume_updated.connect((volume) => {trackt.track_bin.set_volume(volume);});
+ trackt.panorama_updated.connect((panorama) => {trackt.track_bin.set_panorama(panorama);});
+ trackt.eq_updated.connect((band, val) => {trackt.track_bin.set_eq(band, val);});
+ trackt.echo_max_delay_updated.connect((val) => {trackt.track_bin.set_echo_max_delay(val);});
+ trackt.echo_delay_updated.connect((val) => {trackt.track_bin.set_echo_delay(val);});
+ trackt.echo_feedback_updated.connect((val) => {trackt.track_bin.set_echo_feedback(val);});
+ trackt.echo_intensity_updated.connect((val) => {trackt.track_bin.set_echo_intensity(val);});
+ trackt.echo_toggled.connect((val) => {trackt.track_bin.set_echo_active(val);});
+ trackt.dynamics_mode_updated.connect((val) => {trackt.track_bin.set_dynamics_mode(val);});
+ trackt.dynamics_characteristics_updated.connect((val) => {trackt.track_bin.set_dynamics_characteristics(val);});
+ trackt.dynamics_ratio_updated.connect((val) => {trackt.track_bin.set_dynamics_ratio(val);});
+ trackt.dynamics_threshold_updated.connect((val) => {trackt.track_bin.set_dynamics_threshold(val);});
+ trackt.dynamics_toggled.connect((val) => {trackt.track_bin.set_dynamics_active(val);});
+ trackt.active_toggled.connect((val) => {trackt.track.active = val;});
+ this.playback_starting.connect(trackt.playback_starting_callback);
+ this.playback_ending.connect(trackt.playback_ending_callback);
+ this.recording_starting.connect(trackt.recording_starting_callback);
+ this.recording_ending.connect(trackt.recording_ending_callback);
+ trackt.set_active_state(track.active);
+ tracks.pack_start(trackt, false, false, 4);
+ tracks.show_all();
+ }
+
+ public void run() {
+ window.show_all();
+
+ application_settings = new ApplicationSettings();
+ bool first_run = true;
+ string settings_path = "";
+ string errors = "";
+ File settings;
+ // can we get at our settings file?
+ File home_dir = File.new_for_path (Environment.get_home_dir());
+ // do we have a .demorecorder dir
+ File app_dir = File.new_for_path(home_dir.get_path() + "/.demorecorder");
+ if (app_dir.query_exists(null)) {
+ // now check for the existence of the settings file_chooser
+ settings = File.new_for_path(app_dir.get_path() + "/settings.xml");
+ if (settings.query_exists(null)) {
+ first_run = false;
+ }
+ else {
+ // if not assume first run and show settings wizard
+ }
+ }
+ else {
+ int dir_ret = DirUtils.create_with_parents(app_dir.get_path(), 0755);
+ }
+ settings = File.new_for_path(app_dir.get_path() + "/settings.xml");
+ if (first_run) {
+ // create and save settings
+ /// TODO
+ FirstRunWizard dlg = new FirstRunWizard(window, "Demo Recorder");
+ dlg.set_transient_for(window);
+ dlg.run();
+ application_settings = dlg.app_settings;
+ bool saved = ApplicationSettings.save_settings(settings.get_path(), application_settings, ref errors);
+ dlg.destroy();
+ dlg = null;
+ }
+ // may as well load the settings now
+ application_settings = ApplicationSettings.load_settings(settings.get_path(), "1.0", out errors);
+ if (null == application_settings) {
+ // this is major, bail for now
+ Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Failed to create settings!\n%s\nQuiting application.".printf(errors));
+ ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND * 2);
+ stdout.printf(errors + "\n");
+ stdout.flush();
+ window.destroy();
+ exit(-1);
+ }
+ bool has_last_project = (0 < application_settings.last_project_location.length);
+ // we have either old or new settings
+ if (has_last_project) {
+ switch(application_settings.open_action) {
+ case ApplicationSettings.OpenAction.RECENT_PROJECTS:
+ // show recent projects dialog
+ /// TODO
+ load_project(application_settings.last_project_location);
+ break;
+ case ApplicationSettings.OpenAction.NAMED_PROJECT:
+ if (0 < application_settings.named_project_location.length) {
+ load_project(application_settings.named_project_location);
+ }
+ else {
+ // show recent projects dialog
+ /// TODO
+ load_project(application_settings.last_project_location);
+ }
+ break;
+ case ApplicationSettings.OpenAction.LAST_PROJECT:
+ load_project(application_settings.last_project_location);
+ break;
+ default:
+ new_project();
+ break;
+ }
+ }
+ else {
+ new_project();
+ }
+
+ Gtk.main();
+ }
+
+ private bool on_delete_event() {
+ bool ret = false;
+ // do we have a dirty project?
+ if (project_dirty) {
+ // what should we do?
+ switch(application_settings.close_action) {
+ case ApplicationSettings.CloseAction.AUTO_SAVE_PROJECT:
+ save_project();
+ ret = project_dirty;
+ break;
+ case ApplicationSettings.CloseAction.PROMPT_SAVE_PROJECT:
+ YesNoCancelDialog alert = new YesNoCancelDialog(window, "Save changes to current project?", "You have unsaved changes in your current project.\nWould you like to save them first?", Gtk.ResponseType.CANCEL);
+ alert.set_transient_for(window);
+ int yn_ret = alert.run();
+ alert.destroy();
+ alert = null;
+ // yes - save_project()
+ if (Gtk.ResponseType.YES == yn_ret) {
+ save_project();
+ ret = project_dirty;
+ break;
+ }
+ // cancel - return and do nothing
+ else if (Gtk.ResponseType.NO == yn_ret) {
+ ret = false;
+ break;
+ }
+ else { //(Gtk.ResponseType.CANCEL != ret) {
+ ret = true;
+ break;
+ }
+ default: // DO_NOTHING
+ ret = false;
+ break;
+ }
+ }
+ return ret;
+ }
+
+}
+
+}