1 /*
2  * Copyright (C) 2020-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "audio/engine.h"
21 #include "audio/exporter.h"
22 #include "audio/marker_track.h"
23 #include "gui/backend/timeline_selections.h"
24 #include "gui/backend/tracklist_selections.h"
25 #include "gui/widgets/bounce_step_selector.h"
26 #include "gui/widgets/dialogs/bounce_dialog.h"
27 #include "gui/widgets/dialogs/export_progress_dialog.h"
28 #include "project.h"
29 #include "settings/settings.h"
30 #include "utils/flags.h"
31 #include "utils/io.h"
32 #include "utils/resources.h"
33 #include "utils/ui.h"
34 #include "zrythm_app.h"
35 
36 #include <gtk/gtk.h>
37 
G_DEFINE_TYPE(BounceDialogWidget,bounce_dialog_widget,GTK_TYPE_DIALOG)38 G_DEFINE_TYPE (
39   BounceDialogWidget,
40   bounce_dialog_widget,
41   GTK_TYPE_DIALOG)
42 
43 static void
44 on_cancel_clicked (GtkButton * btn,
45                    BounceDialogWidget * self)
46 {
47   gtk_window_close (GTK_WINDOW (self));
48   /*gtk_widget_destroy (GTK_WIDGET (self));*/
49 }
50 
51 static void
on_bounce_clicked(GtkButton * btn,BounceDialogWidget * self)52 on_bounce_clicked (
53   GtkButton * btn,
54   BounceDialogWidget * self)
55 {
56   ExportSettings settings;
57 
58   switch (self->type)
59     {
60     case BOUNCE_DIALOG_REGIONS:
61       settings.mode = EXPORT_MODE_REGIONS;
62       break;
63     case BOUNCE_DIALOG_TRACKS:
64       settings.mode = EXPORT_MODE_TRACKS;
65       break;
66     }
67 
68   if (self->bounce_to_file)
69     {
70       /* TODO */
71     }
72   else
73     {
74       export_settings_set_bounce_defaults (
75         &settings, NULL, self->bounce_name);
76     }
77 
78   Position start_pos;
79   position_init (&start_pos);
80   switch (self->type)
81     {
82     case BOUNCE_DIALOG_REGIONS:
83       timeline_selections_mark_for_bounce (
84         TL_SELECTIONS,
85         settings.bounce_with_parents);
86       settings.mode = EXPORT_MODE_REGIONS;
87       arranger_selections_get_start_pos (
88         (ArrangerSelections *) TL_SELECTIONS,
89         &start_pos, F_GLOBAL);
90       break;
91     case BOUNCE_DIALOG_TRACKS:
92       {
93         tracklist_selections_mark_for_bounce (
94           TRACKLIST_SELECTIONS,
95           settings.bounce_with_parents,
96           F_NO_MARK_MASTER);
97         settings.mode = EXPORT_MODE_TRACKS;
98 
99         /* start at start marker */
100         Marker * m =
101           marker_track_get_start_marker (
102             P_MARKER_TRACK);
103         ArrangerObject * m_obj =
104           (ArrangerObject *) m;
105         position_set_to_pos (
106           &start_pos, &m_obj->pos);
107       }
108       break;
109     }
110 
111   /* start exporting in a new thread */
112   GThread * thread =
113     g_thread_new (
114       "bounce_thread",
115       (GThreadFunc) exporter_generic_export_thread,
116       &settings);
117 
118   /* create a progress dialog and block */
119   ExportProgressDialogWidget * progress_dialog =
120     export_progress_dialog_widget_new (
121       &settings, true, false, F_CANCELABLE);
122   gtk_window_set_transient_for (
123     GTK_WINDOW (progress_dialog),
124     GTK_WINDOW (self));
125   gtk_dialog_run (GTK_DIALOG (progress_dialog));
126   gtk_widget_destroy (GTK_WIDGET (progress_dialog));
127 
128   g_thread_join (thread);
129 
130   if (!self->bounce_to_file &&
131       !settings.progress_info.has_error &&
132       !settings.progress_info.cancelled)
133     {
134       /* create audio track with bounced material */
135       exporter_create_audio_track_after_bounce (
136         &settings, &start_pos);
137     }
138 
139   export_settings_free_members (&settings);
140 
141   gtk_window_close (GTK_WINDOW (self));
142 }
143 
144 static void
on_tail_value_changed(GtkSpinButton * spin,BounceDialogWidget * self)145 on_tail_value_changed (
146   GtkSpinButton *      spin,
147   BounceDialogWidget * self)
148 {
149   g_settings_set_int (
150     S_UI, "bounce-tail",
151     gtk_spin_button_get_value_as_int (spin));
152 }
153 
154 static void
on_bounce_with_parents_toggled(GtkToggleButton * btn,BounceDialogWidget * self)155 on_bounce_with_parents_toggled (
156   GtkToggleButton *    btn,
157   BounceDialogWidget * self)
158 {
159   bool toggled = gtk_toggle_button_get_active (btn);
160   gtk_widget_set_visible (
161     GTK_WIDGET (self->bounce_step_box), !toggled);
162   g_settings_set_boolean (
163     S_UI, "bounce-with-parents", toggled);
164 }
165 
166 static void
on_disable_after_bounce_toggled(GtkToggleButton * btn,BounceDialogWidget * self)167 on_disable_after_bounce_toggled (
168   GtkToggleButton *    btn,
169   BounceDialogWidget * self)
170 {
171   bool toggled = gtk_toggle_button_get_active (btn);
172   g_settings_set_boolean (
173     S_UI, "disable-after-bounce", toggled);
174 }
175 
176 /**
177  * Creates a bounce dialog.
178  */
179 BounceDialogWidget *
bounce_dialog_widget_new(BounceDialogWidgetType type,const char * bounce_name)180 bounce_dialog_widget_new (
181   BounceDialogWidgetType type,
182   const char *           bounce_name)
183 {
184   BounceDialogWidget * self =
185     g_object_new (BOUNCE_DIALOG_WIDGET_TYPE, NULL);
186 
187   self->type = type;
188   /* TODO free when destroying */
189   self->bounce_name = g_strdup (bounce_name);
190 
191   self->bounce_step_selector =
192     bounce_step_selector_widget_new ();
193   gtk_container_add (
194     GTK_CONTAINER (self->bounce_step_box),
195     GTK_WIDGET (self->bounce_step_selector));
196 
197   g_signal_connect (
198     G_OBJECT (self->tail_spin), "value-changed",
199     G_CALLBACK (on_tail_value_changed), self);
200   g_signal_connect (
201     G_OBJECT (self->bounce_with_parents), "toggled",
202     G_CALLBACK (on_bounce_with_parents_toggled),
203     self);
204 
205   if (type == BOUNCE_DIALOG_TRACKS)
206     {
207       g_signal_connect (
208         G_OBJECT (self->disable_after_bounce),
209         "toggled",
210         G_CALLBACK (on_disable_after_bounce_toggled),
211         self);
212     }
213   else
214     {
215       /* hide 'disable after bounce' if not bouncing
216        * tracks */
217       gtk_widget_set_visible (
218         GTK_WIDGET (self->disable_after_bounce),
219         false);
220     }
221 
222   return self;
223 }
224 
225 static void
bounce_dialog_widget_class_init(BounceDialogWidgetClass * _klass)226 bounce_dialog_widget_class_init (
227   BounceDialogWidgetClass * _klass)
228 {
229   GtkWidgetClass * klass = GTK_WIDGET_CLASS (_klass);
230   resources_set_class_template (
231     klass, "bounce_dialog.ui");
232 
233 #define BIND_CHILD(x) \
234   gtk_widget_class_bind_template_child ( \
235     klass, BounceDialogWidget, x)
236 
237   BIND_CHILD (cancel_btn);
238   BIND_CHILD (bounce_btn);
239   BIND_CHILD (bounce_with_parents);
240   BIND_CHILD (bounce_step_box);
241   BIND_CHILD (tail_spin);
242   BIND_CHILD (disable_after_bounce);
243 
244   gtk_widget_class_bind_template_callback (
245     klass, on_cancel_clicked);
246   gtk_widget_class_bind_template_callback (
247     klass, on_bounce_clicked);
248 }
249 
250 static void
bounce_dialog_widget_init(BounceDialogWidget * self)251 bounce_dialog_widget_init (
252   BounceDialogWidget * self)
253 {
254   gtk_widget_init_template (GTK_WIDGET (self));
255 
256   gtk_spin_button_set_value (
257     self->tail_spin,
258     (double)
259     g_settings_get_int (S_UI, "bounce-tail"));
260 
261   bool bounce_with_parents =
262     g_settings_get_boolean (
263       S_UI, "bounce-with-parents");
264   gtk_toggle_button_set_active (
265     GTK_TOGGLE_BUTTON (self->bounce_with_parents),
266     bounce_with_parents);
267   gtk_widget_set_visible (
268     GTK_WIDGET (self->bounce_step_box),
269     !bounce_with_parents);
270 
271   bool disable_after_bounce =
272     g_settings_get_boolean (
273       S_UI, "disable-after-bounce");
274   gtk_toggle_button_set_active (
275     GTK_TOGGLE_BUTTON (self->disable_after_bounce),
276     disable_after_bounce);
277 }
278