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