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