Reworked track settings.
[demorecorder] / src / DemoRecorder.vala
1 /*  Demo Recorder for MAEMO 5
2 *   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
3 *   This program is free software; you can redistribute it and/or modify
4 *   it under the terms of the GNU General Public License version 2,
5 *   or (at your option) any later version, as published by the Free
6 *   Software Foundation
7 *
8 *   This program is distributed in the hope that it will be useful,
9 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
10 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 *   GNU General Public License for more details
12 *
13 *   You should have received a copy of the GNU General Public
14 *   License along with this program; if not, write to the
15 *   Free Software Foundation, Inc.,
16 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 */
18 namespace IdWorks {
19
20 public class DemoRecorder : Hildon.Program {
21   
22   ApplicationSettings application_settings;
23   Hildon.StackableWindow window;
24   Gtk.VBox tracks;
25   SettingsStructures.ProjectSettings project;
26   bool project_dirty;
27   EncodePipeline encodepipeline;
28   PlayPipeline playpipeline;
29   RecordPipeline recordpipeline;
30   PlayerTransport player;
31   MixerBin mixer;
32   uint track_counter { get { return (null != tracks) ? tracks.get_children().length() : 0; }}
33   bool recording;
34   bool playing;
35   bool encoding;
36   uint screen_timer_id;
37   uint playback_timer_id;
38   uint recording_timer_id;
39   uint encoding_timer_id;
40   ulong recording_position_duration_id;
41   Osso.Context osso_context;
42   //int64 track_position;
43   //int64 record_position;
44   //int64 track_duration;
45   
46   public signal void encoding_starting();
47   public signal void encoding_started();
48   public signal void encoding_ending();
49   public signal void encoding_ended();
50   public signal void recording_starting();
51   public signal void recording_started();
52   public signal void recording_ending();
53   public signal void recording_ended();
54   public signal void playback_starting();
55   public signal void playback_started();
56   public signal void playback_ending();
57   public signal void playback_ended();
58   public signal void playback_pausing();
59   public signal void playback_paused();
60   public signal void playback_resuming();
61   public signal void playback_resumed();
62   public signal void playback_position_duration(int64 position, int64 duration);
63   public signal void recording_position_duration(int64 position, int64 duration);
64   public signal void encoding_position_duration(int64 position, int64 duration);
65   
66   public void playpipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
67     playback_position_duration(position, duration);
68     if (position >= duration) playback_time_elapsed();
69   }
70   public void recordpipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
71     recording_position_duration(position, duration);
72     if (0 < duration && position >= duration) recording_time_elapsed();
73   }
74   public void encodepipeline_position_duration_callback(Gst.Pipeline sender, int64 position, int64 duration) {
75     encoding_position_duration(position, duration);
76     if (position >= duration) encoding_time_elapsed();
77   }
78   private bool update_time_and_duration_play_pipeline() {
79     playpipeline.get_duration_info();
80     return true;
81   }
82   private bool update_time_and_duration_record_pipeline() {
83     recordpipeline.get_duration_info();
84     return true;
85   }
86   private bool update_time_and_duration_encode_pipeline() {
87     encodepipeline.get_duration_info();
88     return true;
89   }
90   private void recording_time_elapsed() {
91     Source.remove(recording_timer_id);
92     /*
93     this should only ever fire for fixed time 'recording'
94     i.e. bouncing or mixing tracks
95     hence we can use it as a trigger
96     */
97   }
98   private void encoding_time_elapsed() {
99     Source.remove(encoding_timer_id);
100     /*
101     this should only ever fire for fixed time 'recording'
102     i.e. bouncing or mixing tracks
103     hence we can use it as a trigger
104     */
105   }
106   private void playback_time_elapsed() {
107     if (!this.recording) Source.remove(playback_timer_id);
108   }
109   
110   construct {
111     osso_context = new Osso.Context("DemoRecorder", "0.1", false, null);
112     project = SettingsStructures.ProjectSettings();
113     recording = false;
114     playing = false;
115     window = new Hildon.StackableWindow();
116     window.destroy.connect(Gtk.main_quit);
117     window.delete_event.connect(on_delete_event);
118     window.set_title("Demo Recorder");
119     construct_menu();
120     Gtk.VBox container = new Gtk.VBox(false, 0);
121     Hildon.PannableArea scrollwin = new Hildon.PannableArea();
122     tracks = new Gtk.VBox(false, 0);
123     player = new PlayerTransport();
124     player.window = this.window;
125     this.playback_position_duration.connect(player.position_duration_callback);
126     player.play_clicked.connect(mixer_play_all);
127     player.pause_toggled.connect(mixer_pause);
128     player.stop_clicked.connect(mixer_stop);
129     player.volume_updated.connect(mixer_volume_updated);
130     player.panorama_updated.connect(mixer_panorama_updated);
131     player.eq_updated.connect(mixer_eq_updated);
132     player.record_toggled.connect(mixer_record);
133     container.pack_start(player, false, false, 0);
134     scrollwin.add_with_viewport(tracks);
135     container.pack_end(scrollwin, true, true, 0);
136     window.add(container);
137     mixer = new MixerBin("mixer");
138     //playpipeline = new PlayPipeline("mixer");
139   }
140   
141   private void mixer_volume_updated(PlayerTransport sender, double volume) {
142     mixer.set_volume(volume);
143   }
144   
145   private void mixer_panorama_updated(PlayerTransport sender, double panorama) {
146     mixer.set_panorama(panorama);
147   }
148   
149   private void mixer_eq_updated(PlayerTransport sender, int band, double val) {
150     mixer.set_eq(band, val);
151   }
152   
153   private bool keep_screen_on() {
154     if (this.recording || this.playing) {
155       Osso.Status status = this.osso_context.display_blanking_pause();
156       /*if (Osso.Status.OK == status) {
157         stdout.printf("%s\n", "OK");
158       }
159       else if (Osso.Status.INVALID == status) {
160         stdout.printf("%s\n", "INVALID");
161       }
162       else if (Osso.Status.ERROR == status) {
163         stdout.printf("%s\n", "ERROR");
164       }
165       else {
166         stdout.printf("%s\n", "UNKNOWN");
167       }
168       stdout.flush();*/
169       return true;
170     }
171     else {
172       Source.remove(screen_timer_id);
173       return false;
174     }
175   }
176   
177   private void mixer_play_all() {
178     /// TODO clean up old mixer first?
179     mixer = new MixerBin("mixer");
180     int idx = 0;
181     if (0 < project.tracks.length()) {
182       this.playing = true;
183       keep_screen_on();
184       if (0 != screen_timer_id) Source.remove(screen_timer_id);
185       screen_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND * 20, keep_screen_on);
186       var children = tracks.get_children();
187       foreach (var child in children) {
188         TrackTransport tt = child as TrackTransport;
189         if (null != tt && tt.get_active_state()) {
190           mixer.add_track(tt.track_bin);
191         }
192       }
193       //for (int i = 0; i < tracks.children.length(); ++i) {
194          //Object tt = tracks.children.nth_data(i);
195          //stdout.printf("Type %s\n",tt.get_type().name());
196          //if (null != tt) {
197            //if (tt.track_bin.IsActive) {
198              //stdout.printf("Track %s active and has been added\n", i.to_string());
199              //track.bin.set_start_time((Gst.ClockTime)(Time.Nanoseconds.SECOND * idx));
200              //mixer.add_track(tt.track_bin);
201            //}
202          //}
203          //tt = null;
204        //}
205        /*while (idx < project.tracks.length()) {
206          Track track = project.tracks.nth_data(idx);
207          if (track.active) {
208            //stdout.printf("Track %s active and has been added\n", idx.to_string());
209            stdout.printf("Track_bin %s active and has been added\n", track.bin.IsActive.to_string());
210            //track.bin.set_start_time((Gst.ClockTime)(Time.Nanoseconds.SECOND * idx));
211            mixer.add_track(track.bin);
212          }
213          ++idx;
214        }*/
215       playpipeline = new PlayPipeline("player", mixer);
216       this.playback_position_duration.connect(player.position_duration_callback);
217       playpipeline.position_duration.connect(playpipeline_position_duration_callback);
218       playback_starting();
219       playpipeline.play();
220       playback_started();
221       update_time_and_duration_play_pipeline();
222       if (0 != playback_timer_id) Source.remove(playback_timer_id);
223       playback_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND / 10, update_time_and_duration_play_pipeline);
224     }
225     if (this.recording) {
226       if (!this.playing) {
227         keep_screen_on();
228         if (0 != screen_timer_id) Source.remove(screen_timer_id);
229         screen_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND * 20, keep_screen_on);
230       }
231       this.playback_position_duration.disconnect(player.position_duration_callback);
232       this.recording_position_duration.connect(player.position_duration_callback);
233       recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
234       recording_starting();
235       recordpipeline.record();
236       recording_started();
237       update_time_and_duration_record_pipeline();
238       if (0 != recording_timer_id) Source.remove(recording_timer_id);
239       recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND / 10, update_time_and_duration_record_pipeline);
240     }
241   }
242   
243   private void mixer_play() {
244     this.playing = true;
245     playback_resuming();
246     playpipeline.play();
247     playback_resumed();
248   }
249   
250   private void mixer_pause(bool val) {
251     if (val) {
252       playback_pausing();
253       playpipeline.pause();
254       playback_paused();
255     }
256     else {
257       playback_resuming();
258       playpipeline.play();
259       playback_resumed();
260     }
261   }
262   
263   private void mixer_record(bool val) {
264     if (val) {
265       prepare_record();
266     }
267     else {
268       this.recording = false;
269     }
270   }
271   
272   private void mixer_stop() {
273     if (this.recording) {
274       recording_ending();
275       this.recording = false;
276       recordpipeline.stop();
277       //this.import_track_from_uri(Filename.to_uri(recordpipeline.get_location()));
278       Track track = Track.with_uri(Filename.to_uri(recordpipeline.get_location()), "Track-" + (project.last_track_number).to_string(), true, true, 1.0, 0.0);
279       project.tracks.append(track);
280       add_track_transport(track);
281       recordpipeline = null;
282       recording_ended();
283     }
284     if (this.playing) {
285       playback_ending();
286       playpipeline.stop();
287       playpipeline = null;
288       this.playing = false;
289       playback_ended();
290     }
291     //stdout.flush();
292   }
293   
294   private void construct_menu() {
295     Hildon.AppMenu menu = new Hildon.AppMenu();
296     var btnNew  = new Gtk.Button.with_label("New project");
297     btnNew.clicked.connect(new_project);
298     menu.append(btnNew);
299     var btnOpen  = new Gtk.Button.with_label("Open project");
300     btnOpen.clicked.connect(open_project);
301     menu.append(btnOpen);
302     var btnSave  = new Gtk.Button.with_label("Save project");
303     btnSave.clicked.connect(save_project);
304     menu.append(btnSave);
305     var btnImport  = new Gtk.Button.with_label("Import track");
306     btnImport.clicked.connect(open_import);
307     menu.append(btnImport);
308     var btnStartACM  = new Gtk.Button.with_label("Input settings");
309     btnStartACM.clicked.connect(start_acm);
310     menu.append(btnStartACM);
311     var btnBounce  = new Gtk.Button.with_label("Bounce tracks");
312     btnBounce.clicked.connect(prepare_bounce);
313     menu.append(btnBounce);
314     var btnMixdown  = new Gtk.Button.with_label("Mixdown");
315     btnMixdown.clicked.connect(prepare_mixdown);
316     menu.append(btnMixdown);
317     var btnSettings  = new Gtk.Button.with_label("Settings");
318     btnSettings.clicked.connect(show_settings);
319     menu.append(btnSettings);
320     var btnAbout = new Gtk.Button.with_label("About");
321     btnAbout.clicked.connect(show_about);
322     menu.append(btnAbout);
323     menu.show_all();
324     window.set_app_menu(menu);
325   }
326   private void show_about() {
327     AboutDialog dlg = new AboutDialog(window);
328     dlg.set_transient_for(window);
329     dlg.run();
330     dlg.destroy();
331     dlg = null;
332   }
333   private void show_settings() {
334     File settings = File.new_for_path(Environment.get_home_dir() + "/.demorecorder/settings.xml");
335     string errors = "";
336     FirstRunWizard dlg = new FirstRunWizard(window, "Demo Recorder");
337     dlg.set_transient_for(window);
338     dlg.app_settings = application_settings;
339     dlg.run();
340     application_settings = dlg.app_settings;
341     bool saved = ApplicationSettings.save_settings(settings.get_path(), application_settings, ref errors);
342     dlg.destroy();
343     dlg = null;
344   }
345   private void new_project() {
346     bool good = false;
347     // is the current project dirty?
348     if (project_dirty) {
349       // run an are you sure dialog
350       // Do you want to save your current project first? Yes, No, Cancel
351       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);
352       alert.set_transient_for(window);
353       int ret = alert.run();
354       alert.destroy();
355       alert = null;
356       // yes - save_project()
357       if (Gtk.ResponseType.YES == ret) {
358         save_project();
359       }
360       // cancel - return and do nothing
361       else if (Gtk.ResponseType.CANCEL == ret || Gtk.ResponseType.NO != ret) {
362         return;
363       }
364       // carry on and create_project()
365     }
366     ProjectSettingsDialog dlg = new ProjectSettingsDialog.with_values(window, "New project settings", "New project", "", "", application_settings.working_directory);
367     dlg.set_transient_for(window);
368     int ret = dlg.run();
369     if (Gtk.ResponseType.OK == ret) {
370       good = true;
371       this.project = SettingsStructures.ProjectSettings();
372       this.project.name = dlg.get_project_name();
373       this.project.working_directory = dlg.get_project_directory();
374       // ensure that the directories exist.
375       DirUtils.create_with_parents(this.project.working_directory + "/tracks", 0755);
376       DirUtils.create_with_parents(this.project.working_directory + "/mixes", 0755);
377       DirUtils.create_with_parents(this.project.working_directory + "/tmp", 0755);
378       this.project.location = dlg.get_project_filename();
379       save_project();
380     }
381     dlg.destroy();
382     dlg = null;
383     if (good) {
384       // Now should clean up the display in preparation for working on this new project.
385       //load_project(this.project.location);
386       application_settings.last_project_name = project.name;
387       application_settings.last_project_location = project.location;
388       window.set_title(project.name);
389       remove_all_tracks();
390     }
391   }
392   
393   private void open_project() {
394     // is the current project dirty?
395     if (project_dirty) {
396       // run an are you sure dialog
397       // Do you want to save your current project first? Yes, No, Cancel
398       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);
399       alert.set_transient_for(window);
400       int ret = alert.run();
401       alert.destroy();
402       alert = null;
403       // yes - save_project()
404       if (Gtk.ResponseType.YES == ret) {
405         save_project();
406       }
407       // cancel - return and do nothing
408       else if (Gtk.ResponseType.CANCEL == ret || Gtk.ResponseType.NO != ret) {
409         return;
410       }
411       // carry on and load_project()
412     }
413     // get a filename
414     Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.OPEN);
415     file_chooser.set_show_upnp(false);
416     file_chooser.set_current_folder(application_settings.working_directory);
417     int ret = file_chooser.run();
418     if (Gtk.ResponseType.OK == ret) {
419       load_project(file_chooser.get_filename());
420     }
421     file_chooser.destroy ();
422     file_chooser = null;
423   }
424   
425   private void load_project(string location) {
426     remove_all_tracks();
427     project = SettingsStructures.ProjectSettings();
428     string errors = "";
429     if (XmlHelpers.parse_project_settings_file(location, "1.0", ref project, ref errors)) {
430       // need to tear down and build up the interface again.
431       // as a test let's save it for inspection
432       //save_project_settings(project.location);
433       /// TODO
434       application_settings.last_project_name = project.name;
435       application_settings.last_project_location = project.location;
436       if (ApplicationSettings.save_settings(Environment.get_home_dir() + "/.demorecorder/settings.xml", application_settings, ref errors)) {
437         window.set_title(project.name);
438         for (int idx = 0; idx < project.tracks.length(); ++idx) {
439           add_track_transport((Track)project.tracks.nth_data(idx));
440         }
441       }
442       else {
443         stdout.printf("%s\n", errors);
444         stdout.flush();
445       }
446     }
447     else {
448       Hildon.Note note = new Hildon.Note.information(window, "Project couldn't be opened!\n%s".printf(errors));
449       note.run();
450       note.destroy();
451       note = null;
452     }
453   }
454   
455   private void remove_all_tracks() {
456     List<Gtk.Widget> children = tracks.get_children();
457     for (int idx = 0; idx < children.length(); ++idx) {
458       tracks.remove((Gtk.Widget)children.nth_data(idx));
459     }
460     for (int idx = ((int)project.tracks.length()) - 1; idx > -1; --idx) {
461       project.tracks.delete_link(project.tracks.nth(idx));
462     }
463     player.position_duration_callback(0, 0);
464   }
465   private void remove_track_interactive(TrackTransport tt) {
466     string message = "";
467     string ns = "";
468      switch(application_settings.remove_track_action) {
469       case ApplicationSettings.RemoveTrackAction.DELETE_FILE:
470         remove_track(tt);
471         message = "Track deleted.";
472         FileUtils.remove(Filename.from_uri(tt.track.uri, out ns));
473         break;
474       case ApplicationSettings.RemoveTrackAction.PRESERVE_FILE:
475         remove_track(tt);
476         message = "Track removed.";
477         break;
478       case ApplicationSettings.RemoveTrackAction.PROMPT:
479         YesNoCancelDialog dlg = new YesNoCancelDialog(window, "Remove Track", "Do you want to delete the source file permanently?", Gtk.ResponseType.CANCEL);
480         dlg.set_transient_for(window);
481         int ret = dlg.run();
482         dlg.destroy();
483         if (Gtk.ResponseType.YES == ret) {
484           remove_track(tt);
485           message = "Track deleted.";
486           FileUtils.remove(Filename.from_uri(tt.track.uri, out ns));
487         }
488         else if (Gtk.ResponseType.NO == ret) {
489           remove_track(tt);
490           message = "Track removed.";
491         }
492         break;
493        default:
494          break;
495      }
496     if ("" != message) {
497       Hildon.Note note = new Hildon.Note.information(window, "%s".printf(message));
498       note.run();
499       note.destroy();
500       note = null;
501     }
502   }
503   private void remove_track(TrackTransport tt) {
504     project.tracks.remove(tt.track);
505     tracks.remove(tt);
506     // rebuild project.tracks
507     for (int idx = (int)project.tracks.length(); idx >= 0; --idx) {
508       project.tracks.remove_link(project.tracks.nth(idx));
509     }
510     foreach (var child in tracks.get_children()) {
511       project.tracks.append(((TrackTransport)child).track);
512     }
513     project_dirty = true;
514     tt = null;
515   }
516   
517   private void save_project() {
518     // rebuild track settings
519     for (int idx = (int)project.tracks.length(); idx >= 0; --idx) {
520       project.tracks.remove_link(project.tracks.nth(idx));
521     }
522     foreach (var child in tracks.get_children()) {
523       project.tracks.append(((TrackTransport)child).track);
524     }
525     if (0 < project.location.length) {
526       save_project_settings(project.location);
527     }
528     else {
529       do_save_as_project();
530     }
531     //stdout.printf(project.to_xml_string());
532   }
533   private void do_save_as_project() {
534     // get a filename
535     Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.SAVE);
536     file_chooser.set_show_upnp(false);
537     int ret = file_chooser.run();
538     if (Gtk.ResponseType.OK == ret) {
539       project.location = file_chooser.get_filename();
540       save_project_settings(project.location);
541     }
542     file_chooser.destroy ();
543     file_chooser = null;
544   }
545   private void save_project_settings(string location) {
546     bool success = false;
547     Hildon.gtk_window_set_progress_indicator(window, (uint)true);
548     string contents = project.to_xml_string();
549     // should ask each ui element for it's updated settings to be on the safe side first.
550     /// TODO
551     if (0 < contents.length)
552     {
553       contents = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + contents;
554       if (FileUtils.set_contents(location, contents, contents.length)) {
555         project_dirty = false;
556         // alert user of success? no!
557         //Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Project saved.");
558         //((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
559       }
560       else
561       {
562         // alert user of failure to save file.
563         Hildon.Note alert = new Hildon.Note.information(window, "Project couldn't be saved!\nFailed to save the project settings file.");
564         alert.run();
565         alert.destroy();
566         alert = null;
567       }
568     }
569     else {
570       // alert user of failure to create serialize settings.
571       Hildon.Note alert = new Hildon.Note.information(window, "Project couldn't be saved!\nFailed to serialize the project settings.");
572       alert.run();
573       alert.destroy();
574       alert = null;
575     }
576     Hildon.gtk_window_set_progress_indicator(window, (uint)false);
577   }
578   
579   private void start_acm() {
580     AcmPopUp acm = new AcmPopUp.with_settings("Input Settings", window, project.monitor);
581     acm.set_transient_for(window);
582     acm.play();
583     if (Gtk.ResponseType.OK == acm.run()) {
584       project.monitor = acm.get_settings_structure();
585       project_dirty = true;
586     }
587     acm.stop();
588     acm.destroy();
589     acm = null;
590   }
591   
592   private void prepare_mixdown() {
593     MixdownPopUp dlg = new MixdownPopUp.with_settings("Mixdown your track", window, project.mixdown);
594     dlg.set_transient_for(window);
595     if (Gtk.ResponseType.OK == dlg.run()) {
596       project.mixdown = dlg.get_settings_structure();
597       project_dirty = true;
598       do_mixdown();
599     }
600     dlg.destroy();
601     dlg = null;
602   }
603   
604   private void do_mixdown() {
605     Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
606                                          "rate", typeof (int), 48000,
607                                          "channels", typeof (int), 2,
608                                          "depth", typeof (int), 16);
609     mixer = new MixerBin("mixer");
610     int idx = 0;
611     if (0 < project.tracks.length()) {
612       while (idx < project.tracks.length()) {
613         Track track = project.tracks.nth_data(idx);
614         if (track.active) {
615           mixer.add_track(track.bin);
616         }
617         ++idx;
618       }
619       this.recording = false;
620       this.playing = false;
621       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));
622       if (null != encoder) {
623         encodepipeline = new EncodePipeline("mixdown", project.mixdown.location, mixer, encoder, caps);
624         encodepipeline.position_duration.connect(encodepipeline_position_duration_callback);
625         Hildon.gtk_window_set_progress_indicator(window, (uint)true);
626         encoding_starting();
627         encodepipeline.encode();
628         encoding_started();
629         update_time_and_duration_encode_pipeline();
630         if (0 != recording_timer_id) Source.remove(encoding_timer_id);
631         encoding_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_encode_pipeline);
632         ProgressPopUp dlg = new ProgressPopUp("Mixing down", window);
633         dlg.set_message("Mixing down your track.\nThis may take some time depending on the duration of the audio.\n");
634         dlg.set_transient_for(window);
635         this.encoding_position_duration.connect((p,d) => {
636           double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND)); 
637           dlg.set_progress(percent);
638           dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
639           //if (p >= d) {
640           //  dlg.close_me();
641           //  this.mixer_stop();
642           //}
643         });
644         encodepipeline.end_of_stream.connect((e) => {encoding_ending();dlg.close_me();encoding_time_elapsed();this.mixer_stop();encoding_ended();});
645         if (Gtk.ResponseType.CANCEL == dlg.run()) {
646           // Should really stop the mixer and delete the files!
647           /// TODO
648           this.mixer_stop();
649         }
650         dlg.destroy();
651         dlg = null;
652         //encoder = null;
653         Hildon.gtk_window_set_progress_indicator(window, (uint)false);
654         Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Mixdown complete");
655         ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
656       }
657       else {
658         Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Mixdown failed! Encoder not available.");
659         ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
660       }
661     }
662   }
663   
664   private void prepare_bounce() {
665     project.last_track_number++;
666     string new_location = "%s/tracks/bounce-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
667     string name = "bounce-" + project.last_track_number.to_string();
668     do_bounce(new_location);
669   }
670   
671   private void do_bounce(string filename) {
672     Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
673                                          "rate", typeof (int), 48000,
674                                          "channels", typeof (int), 2,
675                                          "depth", typeof (int), 16);
676     //Gst.Element src = Gst.ElementFactory.make("pulsesrc", "input");
677     //src.set_property("device", "source.hw0");
678     mixer = new MixerBin("mixer");
679     int idx = 0;
680     if (0 < project.tracks.length()) {
681       while (idx < project.tracks.length()) {
682         Track track = project.tracks.nth_data(idx);
683         if (track.active) {
684           mixer.add_track(track.bin);
685         }
686         ++idx;
687       }
688       this.recording = true;
689       this.playing = false;
690       recordpipeline = new RecordPipeline("bouncer", filename, mixer, caps, "wavenc");
691       recordpipeline.bouncing = true;
692       recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
693       Hildon.gtk_window_set_progress_indicator(window, (uint)true);
694       recording_starting();
695       recordpipeline.record();
696       recording_started();
697       update_time_and_duration_record_pipeline();
698       if (0 != recording_timer_id) Source.remove(recording_timer_id);
699       recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_record_pipeline);
700       ProgressPopUp dlg = new ProgressPopUp("Bouncing tracks", window);
701       dlg.set_transient_for(window);
702       dlg.set_message("Bouncing your selected tracks.\nThis may take some time depending on the duration of the audio.\n");
703       this.recording_position_duration.connect((p,d) => {
704         double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND)); 
705         dlg.set_progress(percent);
706         dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
707         if (p >= d) {
708           dlg.close_me();
709           this.mixer_stop();
710           this.recording = false;
711           if (0 != recording_timer_id) Source.remove(recording_timer_id);
712         }
713       });
714       if (Gtk.ResponseType.CANCEL == dlg.run()) {
715         // Should really stop the mixer and delete the files!
716         /// TODO
717         this.recording = false;
718         this.mixer_stop();
719       }
720       dlg.destroy();
721       dlg = null;
722       Hildon.gtk_window_set_progress_indicator(window, (uint)false);
723       Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Bounced track added to mixer");
724       ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
725     }
726   }
727   
728   public void prepare_record() {
729     project.last_track_number++;
730     string new_location = "%s/tracks/track-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
731     string name = "Track-" + project.last_track_number.to_string();
732     do_record(name, new_location);
733   }
734   
735   public void do_record(string name, string filename) {
736     Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
737                                          "rate", typeof (int), 48000,
738                                          "channels", typeof (int), 2,
739                                          "depth", typeof (int), 16);
740     InputBin src = new InputBin.with_settings("input", project.monitor.input);
741     //Gst.Element src = Gst.ElementFactory.make("pulsesrc", "input");
742     //src.set_property("device", "source.hw0");
743     recordpipeline = new RecordPipeline("recorder", filename, src, caps, "wavenc");
744     recordpipeline.bouncing = false;
745     this.recording = true;
746     //recordpipeline.record();
747   }
748   
749   public void open_import() {
750     Hildon.FileChooserDialog file_chooser = new Hildon.FileChooserDialog(window, Gtk.FileChooserAction.OPEN);
751     file_chooser.set_show_upnp(false);
752     file_chooser.set_current_folder(application_settings.working_directory);
753     if (file_chooser.run () == Gtk.ResponseType.OK) {
754       import_track_from_uri(file_chooser.get_uri());
755     }
756     file_chooser.destroy ();
757   }
758   
759   public void import_track_from_uri(string uri) {
760     /// TODO - Should become import track from uri and convert to wav and name appropriately
761     ++project.last_track_number;
762     project_dirty = true;
763     Gst.Caps caps = new Gst.Caps.simple ("audio/x-raw-int",
764                                          "rate", typeof (int), 48000,
765                                          "channels", typeof (int), 2,
766                                          "depth", typeof (int), 16);
767     /// TODO check that directories exist
768     string new_location = "%s/tracks/import-%s.wav".printf(project.working_directory, project.last_track_number.to_string());
769     string name = "Import-" + project.last_track_number.to_string();
770     ImportBin import_bin = new ImportBin(name);
771     import_bin.set_uri(uri);
772     
773     /// TODO encode to correct destination and add track
774     this.recording = false;
775     this.playing = false;
776     recordpipeline = new RecordPipeline("importer", new_location, import_bin, caps, "wavenc");
777     recordpipeline.bouncing = true;
778     recordpipeline.position_duration.connect(recordpipeline_position_duration_callback);
779     Hildon.gtk_window_set_progress_indicator(window, (uint)true);
780     recording_starting();
781     recordpipeline.record();
782     recording_started();
783     update_time_and_duration_record_pipeline();
784     if (0 != recording_timer_id) Source.remove(recording_timer_id);
785     recording_timer_id = Timeout.add ((uint)Time.Milliseconds.SECOND, update_time_and_duration_record_pipeline);
786     ProgressPopUp dlg = new ProgressPopUp("Importing track", window);
787     dlg.set_transient_for(window);
788     dlg.set_message("Importing your selected track.\nThis may take some time depending on the duration of the audio.\n");
789     this.recording_position_duration.connect((p,d) => {
790       double percent = ((double)(p / Time.Nanoseconds.MILLISECOND) / (double)(d / Time.Nanoseconds.MILLISECOND)); 
791       dlg.set_progress(percent);
792       dlg.set_progress_text("%s of %s completed".printf(Time.time_to_string(p), Time.time_to_string(d)));
793     });
794     recordpipeline.end_of_stream.connect(() => {
795       dlg.close_me();
796       recordpipeline.stop();
797       if (0 != recording_timer_id) Source.remove(recording_timer_id);
798       Track track = Track.with_uri(Filename.to_uri(new_location), name, true, true, 1.0, 0.0);
799       project.tracks.append(track);
800       add_track_transport(track);
801       this.recording = false;
802     });
803     if (Gtk.ResponseType.CANCEL == dlg.run()) {
804       // Should really stop the mixer and delete the files!
805       /// TODO
806       //Track track = new Track.with_uri(Filename.to_uri(new_location), name, true, true, 1.0, 0.0);
807       //project.tracks.append(track);
808       //add_track_transport(track);
809       recordpipeline.stop();
810       this.recording = false;
811     }
812     dlg.destroy();
813     dlg = null;
814     Hildon.gtk_window_set_progress_indicator(window, (uint)false);
815     Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Imported track added to mixer");
816     ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND);
817   }
818   
819   private void add_track_transport(Track track) {
820     TrackTransport trackt = new TrackTransport();
821     trackt.window = this.window;
822     trackt.track_bin = track.bin;
823     trackt.track = track;
824     trackt.title = track.label; //"Track %n".printf(project.track.length());
825     trackt.name = track.label;
826     trackt.delete_clicked.connect((tt) => {this.remove_track_interactive(tt);});
827     trackt.volume_updated.connect((volume) => {trackt.track_bin.set_volume(volume);});
828     trackt.panorama_updated.connect((panorama) => {trackt.track_bin.set_panorama(panorama);});
829     trackt.eq_updated.connect((band, val) => {trackt.track_bin.set_eq(band, val);});
830     trackt.echo_max_delay_updated.connect((val) => {trackt.track_bin.set_echo_max_delay(val);});
831     trackt.echo_delay_updated.connect((val) => {trackt.track_bin.set_echo_delay(val);});
832     trackt.echo_feedback_updated.connect((val) => {trackt.track_bin.set_echo_feedback(val);});
833     trackt.echo_intensity_updated.connect((val) => {trackt.track_bin.set_echo_intensity(val);});
834     trackt.echo_toggled.connect((val) => {trackt.track_bin.set_echo_active(val);});
835     trackt.dynamics_mode_updated.connect((val) => {trackt.track_bin.set_dynamics_mode(val);});
836     trackt.dynamics_characteristics_updated.connect((val) => {trackt.track_bin.set_dynamics_characteristics(val);});
837     trackt.dynamics_ratio_updated.connect((val) => {trackt.track_bin.set_dynamics_ratio(val);});
838     trackt.dynamics_threshold_updated.connect((val) => {trackt.track_bin.set_dynamics_threshold(val);});
839     trackt.dynamics_toggled.connect((val) => {trackt.track_bin.set_dynamics_active(val);});
840     trackt.active_toggled.connect((val) => {trackt.track.active = val;});
841     this.playback_starting.connect(trackt.playback_starting_callback);
842     this.playback_ending.connect(trackt.playback_ending_callback);
843     this.recording_starting.connect(trackt.recording_starting_callback);
844     this.recording_ending.connect(trackt.recording_ending_callback);
845     trackt.set_active_state(track.active);
846     tracks.pack_start(trackt, false, false, 4);
847     tracks.show_all();
848   }
849   
850   public void run() {
851     window.show_all();
852     
853     application_settings = new ApplicationSettings();
854     bool first_run = true;
855     string settings_path = "";
856     string errors = "";
857     File settings;
858     // can we get at our settings file? 
859     File home_dir = File.new_for_path (Environment.get_home_dir());
860     // do we have a .demorecorder dir
861     File app_dir = File.new_for_path(home_dir.get_path() + "/.demorecorder");
862     if (app_dir.query_exists(null)) {
863       // now check for the existence of the settings file_chooser
864       settings = File.new_for_path(app_dir.get_path() + "/settings.xml");
865       if (settings.query_exists(null)) {
866         first_run = false;
867       }
868       else {
869         // if not assume first run and show settings wizard
870       }
871     }
872     else {
873       int dir_ret = DirUtils.create_with_parents(app_dir.get_path(), 0755);
874     }
875     settings = File.new_for_path(app_dir.get_path() + "/settings.xml");
876     if (first_run) {
877       // create and save settings
878       /// TODO
879       FirstRunWizard dlg = new FirstRunWizard(window, "Demo Recorder");
880       dlg.set_transient_for(window);
881       dlg.run();
882       application_settings = dlg.app_settings;
883       bool saved = ApplicationSettings.save_settings(settings.get_path(), application_settings, ref errors);
884       dlg.destroy();
885       dlg = null;
886     }
887     // may as well load the settings now
888     application_settings = ApplicationSettings.load_settings(settings.get_path(), "1.0", out errors);
889     if (null == application_settings) {
890       // this is major, bail for now
891       Gtk.Widget banner = Hildon.Banner.show_information(window, null, "Failed to create settings!\n%s\nQuiting application.".printf(errors));
892       ((Hildon.Banner)banner).set_timeout((uint)Time.Milliseconds.SECOND * 2);
893       stdout.printf(errors + "\n");
894       stdout.flush();
895       window.destroy();
896       exit(-1);
897     }
898     bool has_last_project = (0 < application_settings.last_project_location.length);
899     // we have either old or new settings
900     if (has_last_project) {
901       switch(application_settings.open_action) {
902         case ApplicationSettings.OpenAction.RECENT_PROJECTS:
903           // show recent projects dialog
904           /// TODO
905           load_project(application_settings.last_project_location);
906           break;
907         case ApplicationSettings.OpenAction.NAMED_PROJECT:
908           if (0 < application_settings.named_project_location.length) {
909             load_project(application_settings.named_project_location);
910           }
911           else {
912             // show recent projects dialog
913             /// TODO
914             load_project(application_settings.last_project_location);
915           }
916           break;
917         case ApplicationSettings.OpenAction.LAST_PROJECT:
918           load_project(application_settings.last_project_location);
919           break;
920         default:
921           new_project();
922           break;
923       }
924     }
925     else {
926       new_project();
927     }
928     
929     Gtk.main();
930   }
931   
932   private bool on_delete_event() {
933     bool ret = false;
934     // do we have a dirty project?
935     if (project_dirty) {
936       // what should we do?
937       switch(application_settings.close_action) {
938         case ApplicationSettings.CloseAction.AUTO_SAVE_PROJECT:
939           save_project();
940           ret = project_dirty;
941           break;
942         case ApplicationSettings.CloseAction.PROMPT_SAVE_PROJECT:
943           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);
944           alert.set_transient_for(window);
945           int yn_ret = alert.run();
946           alert.destroy();
947           alert = null;
948           // yes - save_project()
949           if (Gtk.ResponseType.YES == yn_ret) {
950             save_project();
951             ret = project_dirty;
952             break;
953           }
954           // cancel - return and do nothing
955           else if (Gtk.ResponseType.NO == yn_ret) {
956             ret = false;
957             break;
958           }
959           else { //(Gtk.ResponseType.CANCEL != ret) {
960             ret = true;
961             break;
962           }
963         default: // DO_NOTHING
964           ret = false;
965           break;
966       }
967     }
968     return ret;
969   }
970   
971 }
972
973 }