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