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