1 /* GSequencer - Advanced GTK Sequencer
2  * Copyright (C) 2005-2021 Joël Krähemann
3  *
4  * This file is part of GSequencer.
5  *
6  * GSequencer is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU 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  * GSequencer 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 General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with GSequencer.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <ags/audio/ags_recall_audio.h>
21 
22 #include <ags/audio/ags_automation.h>
23 #include <ags/audio/ags_recall_container.h>
24 
25 #include <math.h>
26 
27 #include <ags/i18n.h>
28 
29 void ags_recall_audio_class_init(AgsRecallAudioClass *recall_audio);
30 void ags_recall_audio_connectable_interface_init(AgsConnectableInterface *connectable);
31 void ags_recall_audio_init(AgsRecallAudio *recall_audio);
32 void ags_recall_audio_set_property(GObject *gobject,
33 				   guint prop_id,
34 				   const GValue *value,
35 				   GParamSpec *param_spec);
36 void ags_recall_audio_get_property(GObject *gobject,
37 				   guint prop_id,
38 				   GValue *value,
39 				   GParamSpec *param_spec);
40 void ags_recall_audio_dispose(GObject *gobject);
41 void ags_recall_audio_finalize(GObject *gobject);
42 
43 void ags_recall_audio_automate(AgsRecall *recall);
44 AgsRecall* ags_recall_audio_duplicate(AgsRecall *recall,
45 				      AgsRecallID *recall_id,
46 				      guint *n_params, gchar **parameter_name, GValue *value);
47 
48 /**
49  * SECTION:ags_recall_audio
50  * @short_description: audio context of recall
51  * @title: AgsRecallAudio
52  * @section_id:
53  * @include: ags/audio/ags_recall_audio.h
54  *
55  * #AgsRecallAudio acts as audio recall.
56  */
57 
58 enum{
59   PROP_0,
60   PROP_AUDIO,
61 };
62 
63 static gpointer ags_recall_audio_parent_class = NULL;
64 static AgsConnectableInterface* ags_recall_audio_parent_connectable_interface;
65 
66 GType
ags_recall_audio_get_type()67 ags_recall_audio_get_type()
68 {
69   static volatile gsize g_define_type_id__volatile = 0;
70 
71   if(g_once_init_enter (&g_define_type_id__volatile)){
72     GType ags_type_recall_audio = 0;
73 
74     static const GTypeInfo ags_recall_audio_info = {
75       sizeof (AgsRecallAudioClass),
76       NULL, /* base_init */
77       NULL, /* base_finalize */
78       (GClassInitFunc) ags_recall_audio_class_init,
79       NULL, /* class_finalize */
80       NULL, /* class_data */
81       sizeof (AgsRecallAudio),
82       0,    /* n_preallocs */
83       (GInstanceInitFunc) ags_recall_audio_init,
84     };
85 
86     static const GInterfaceInfo ags_connectable_interface_info = {
87       (GInterfaceInitFunc) ags_recall_audio_connectable_interface_init,
88       NULL, /* interface_finalize */
89       NULL, /* interface_data */
90     };
91 
92     ags_type_recall_audio = g_type_register_static(AGS_TYPE_RECALL,
93 						   "AgsRecallAudio",
94 						   &ags_recall_audio_info,
95 						   0);
96 
97     g_type_add_interface_static(ags_type_recall_audio,
98 				AGS_TYPE_CONNECTABLE,
99 				&ags_connectable_interface_info);
100 
101     g_once_init_leave(&g_define_type_id__volatile, ags_type_recall_audio);
102   }
103 
104   return g_define_type_id__volatile;
105 }
106 
107 void
ags_recall_audio_class_init(AgsRecallAudioClass * recall_audio)108 ags_recall_audio_class_init(AgsRecallAudioClass *recall_audio)
109 {
110   GObjectClass *gobject;
111   AgsRecallClass *recall;
112   GParamSpec *param_spec;
113 
114   ags_recall_audio_parent_class = g_type_class_peek_parent(recall_audio);
115 
116   /* GObjectClass */
117   gobject = (GObjectClass *) recall_audio;
118 
119   gobject->dispose = ags_recall_audio_dispose;
120   gobject->finalize = ags_recall_audio_finalize;
121 
122   gobject->set_property = ags_recall_audio_set_property;
123   gobject->get_property = ags_recall_audio_get_property;
124 
125   /* properties */
126   /**
127    * AgsRecallAudio:audio:
128    *
129    * The assigned audio.
130    *
131    * Since: 3.0.0
132    */
133   param_spec = g_param_spec_object("audio",
134 				   i18n_pspec("assigned audio"),
135 				   i18n_pspec("The audio object it is assigned to"),
136 				   AGS_TYPE_AUDIO,
137 				   G_PARAM_READABLE | G_PARAM_WRITABLE);
138   g_object_class_install_property(gobject,
139 				  PROP_AUDIO,
140 				  param_spec);
141 
142   /* AgsRecallClass */
143   recall = (AgsRecallClass *) recall_audio;
144 
145   recall->automate = ags_recall_audio_automate;
146 
147   recall->duplicate = ags_recall_audio_duplicate;
148 }
149 
150 void
ags_recall_audio_connectable_interface_init(AgsConnectableInterface * connectable)151 ags_recall_audio_connectable_interface_init(AgsConnectableInterface *connectable)
152 {
153   ags_recall_audio_parent_connectable_interface = g_type_interface_peek_parent(connectable);
154 }
155 
156 void
ags_recall_audio_init(AgsRecallAudio * recall_audio)157 ags_recall_audio_init(AgsRecallAudio *recall_audio)
158 {
159   recall_audio->flags = 0;
160 
161   recall_audio->audio = NULL;
162 }
163 
164 void
ags_recall_audio_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * param_spec)165 ags_recall_audio_set_property(GObject *gobject,
166 			      guint prop_id,
167 			      const GValue *value,
168 			      GParamSpec *param_spec)
169 {
170   AgsRecallAudio *recall_audio;
171 
172   GRecMutex *recall_mutex;
173 
174   recall_audio = AGS_RECALL_AUDIO(gobject);
175 
176   /* get recall mutex */
177   recall_mutex = AGS_RECALL_GET_OBJ_MUTEX(gobject);
178 
179   switch(prop_id){
180   case PROP_AUDIO:
181     {
182       AgsAudio *audio;
183 
184       audio = (AgsAudio *) g_value_get_object(value);
185 
186       g_rec_mutex_lock(recall_mutex);
187 
188       if(recall_audio->audio == audio){
189 	g_rec_mutex_unlock(recall_mutex);
190 
191 	return;
192       }
193 
194       if(recall_audio->audio != NULL){
195 	g_object_unref(recall_audio->audio);
196       }
197 
198       if(audio != NULL){
199 	g_object_ref(audio);
200       }
201 
202       recall_audio->audio = audio;
203 
204       g_rec_mutex_unlock(recall_mutex);
205     }
206     break;
207   default:
208     G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
209     break;
210   }
211 }
212 
213 void
ags_recall_audio_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * param_spec)214 ags_recall_audio_get_property(GObject *gobject,
215 			      guint prop_id,
216 			      GValue *value,
217 			      GParamSpec *param_spec)
218 {
219   AgsRecallAudio *recall_audio;
220 
221   GRecMutex *recall_mutex;
222 
223   recall_audio = AGS_RECALL_AUDIO(gobject);
224 
225   /* get recall mutex */
226   recall_mutex = AGS_RECALL_GET_OBJ_MUTEX(gobject);
227 
228   switch(prop_id){
229   case PROP_AUDIO:
230     {
231       g_rec_mutex_lock(recall_mutex);
232 
233       g_value_set_object(value, recall_audio->audio);
234 
235       g_rec_mutex_unlock(recall_mutex);
236     }
237     break;
238   default:
239     G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
240     break;
241   }
242 }
243 
244 void
ags_recall_audio_dispose(GObject * gobject)245 ags_recall_audio_dispose(GObject *gobject)
246 {
247   AgsRecallAudio *recall_audio;
248 
249   recall_audio = AGS_RECALL_AUDIO(gobject);
250 
251   /* audio */
252   if(recall_audio->audio != NULL){
253     gpointer tmp;
254 
255     tmp = recall_audio->audio;
256 
257     recall_audio->audio = NULL;
258 
259     g_object_unref(tmp);
260   }
261 
262   /* call parent */
263   G_OBJECT_CLASS(ags_recall_audio_parent_class)->dispose(gobject);
264 }
265 
266 void
ags_recall_audio_finalize(GObject * gobject)267 ags_recall_audio_finalize(GObject *gobject)
268 {
269   AgsRecallAudio *recall_audio;
270 
271   recall_audio = AGS_RECALL_AUDIO(gobject);
272 
273   /* audio */
274   if(recall_audio->audio != NULL){
275     gpointer tmp;
276 
277     tmp = recall_audio->audio;
278 
279     recall_audio->audio = NULL;
280 
281     g_object_unref(tmp);
282   }
283 
284   /* call parent */
285   G_OBJECT_CLASS(ags_recall_audio_parent_class)->finalize(gobject);
286 }
287 
288 void
ags_recall_audio_automate(AgsRecall * recall)289 ags_recall_audio_automate(AgsRecall *recall)
290 {
291   AgsAudio *audio;
292 
293   GObject *soundcard;
294 
295   GList *automation_start, *automation;
296   GList *port_start, *port;
297 
298   gdouble delay;
299   guint note_offset, delay_counter;
300 
301   guint loop_left, loop_right;
302   gboolean do_loop;
303 
304   double x, step;
305   guint ret_x;
306   gboolean return_prev_on_failure;
307 
308   GRecMutex *audio_mutex;
309 
310   audio = NULL;
311 
312   soundcard = NULL;
313 
314   port_start = NULL;
315 
316   g_object_get(recall,
317 	       "audio", &audio,
318 	       NULL);
319 
320   audio_mutex = AGS_AUDIO_GET_OBJ_MUTEX(audio);
321 
322   g_rec_mutex_lock(audio_mutex);
323 
324   if(audio->automation_port == NULL){
325     g_rec_mutex_unlock(audio_mutex);
326 
327     if(audio != NULL){
328       g_object_unref(audio);
329     }
330 
331     return;
332   }
333 
334   g_rec_mutex_unlock(audio_mutex);
335 
336   g_object_get(audio,
337 	       "output-soundcard", &soundcard,
338 	       NULL);
339 
340   g_object_get(recall,
341 	       "port", &port_start,
342 	       NULL);
343 
344   /* retrieve position */
345   note_offset = ags_soundcard_get_note_offset(AGS_SOUNDCARD(soundcard));
346 
347   delay = ags_soundcard_get_delay(AGS_SOUNDCARD(soundcard));
348   delay_counter = ags_soundcard_get_delay_counter(AGS_SOUNDCARD(soundcard));
349 
350   /* retrieve loop information */
351   ags_soundcard_get_loop(AGS_SOUNDCARD(soundcard),
352 			 &loop_left, &loop_right,
353 			 &do_loop);
354 
355   return_prev_on_failure = TRUE;
356 
357   if(do_loop &&
358      loop_left <= note_offset){
359     if(note_offset == loop_left){
360       return_prev_on_failure = TRUE;
361     }
362   }
363 
364   /* apply automation */
365   port = port_start;
366 
367   x = ((double) note_offset + (delay_counter / delay)) * ((1.0 / AGS_AUTOMATION_MINIMUM_ACCELERATION_LENGTH) * AGS_NOTATION_MINIMUM_NOTE_LENGTH);
368   step = ((1.0 / AGS_AUTOMATION_MINIMUM_ACCELERATION_LENGTH) * AGS_NOTATION_MINIMUM_NOTE_LENGTH);
369 
370   while(port != NULL){
371     gchar *specifier;
372 
373     gboolean success;
374 
375     g_object_get(AGS_PORT(port->data),
376 		 "specifier", &specifier,
377 		 NULL);
378 
379     g_rec_mutex_lock(audio_mutex);
380 
381     success = g_strv_contains(audio->automation_port, specifier);
382 
383     g_rec_mutex_unlock(audio_mutex);
384 
385     g_free(specifier);
386 
387     if(!success){
388       /* iterate */
389       port = port->next;
390 
391       continue;
392     }
393 
394     g_object_get(AGS_PORT(port->data),
395 		 "automation", &automation_start,
396 		 NULL);
397 
398     /* find offset */
399     automation = automation_start;
400 
401     while(automation != NULL){
402       AgsAutomation *current_automation;
403 
404       AgsTimestamp *timestamp;
405 
406       current_automation = automation->data;
407 
408       /* get some fields */
409       g_object_get(current_automation,
410 		   "timestamp", &timestamp,
411 		   NULL);
412 
413       if(ags_timestamp_get_ags_offset(timestamp) + AGS_AUTOMATION_DEFAULT_OFFSET < x){
414 	automation = automation->next;
415 
416 	g_object_unref(timestamp);
417 
418 	continue;
419       }
420 
421       if(!ags_automation_test_flags(current_automation, AGS_AUTOMATION_BYPASS)){
422 	GValue value = {0,};
423 
424 	ret_x = ags_automation_get_value(current_automation,
425 					 floor(x), ceil(x + step),
426 					 return_prev_on_failure,
427 					 &value);
428 
429 	if(ret_x != G_MAXUINT){
430 	  ags_port_safe_write(port->data,
431 			      &value);
432 	}
433       }
434 
435       if(ags_timestamp_get_ags_offset(timestamp) > ceil(x + step)){
436 	g_object_unref(timestamp);
437 
438 	break;
439       }
440 
441       /* unref */
442       g_object_unref(timestamp);
443 
444       /* iterate */
445       automation = automation->next;
446     }
447 
448     g_list_free_full(automation_start,
449 		     g_object_unref);
450 
451     /* iterate */
452     port = port->next;
453   }
454 
455   if(audio != NULL){
456     g_object_unref(audio);
457   }
458 
459   if(soundcard != NULL){
460     g_object_unref(soundcard);
461   }
462 
463   g_list_free_full(port_start,
464 		   g_object_unref);
465 }
466 
467 AgsRecall*
ags_recall_audio_duplicate(AgsRecall * recall,AgsRecallID * recall_id,guint * n_params,gchar ** parameter_name,GValue * value)468 ags_recall_audio_duplicate(AgsRecall *recall,
469 			   AgsRecallID *recall_id,
470 			   guint *n_params, gchar **parameter_name, GValue *value)
471 {
472   AgsRecallAudio *copy_recall_audio;
473 
474   /* duplicate */
475   copy_recall_audio = AGS_RECALL_AUDIO(AGS_RECALL_CLASS(ags_recall_audio_parent_class)->duplicate(recall,
476 												  recall_id,
477 												  n_params, parameter_name, value));
478 
479   g_warning("ags_recall_audio_duplicate - you shouldn't do this %s", G_OBJECT_TYPE_NAME(recall));
480 
481   return((AgsRecall *) copy_recall_audio);
482 }
483 
484 /**
485  * ags_recall_audio_get_audio:
486  * @recall_audio: the #AgsRecallAudio
487  *
488  * Get audio.
489  *
490  * Returns: (transfer full): the #AgsAudio
491  *
492  * Since: 3.1.0
493  */
494 AgsAudio*
ags_recall_audio_get_audio(AgsRecallAudio * recall_audio)495 ags_recall_audio_get_audio(AgsRecallAudio *recall_audio)
496 {
497   AgsAudio *audio;
498 
499   if(!AGS_IS_RECALL_AUDIO(recall_audio)){
500     return(NULL);
501   }
502 
503   g_object_get(recall_audio,
504 	       "audio", &audio,
505 	       NULL);
506 
507   return(audio);
508 }
509 
510 /**
511  * ags_recall_audio_set_audio:
512  * @recall_audio: the #AgsRecallAudio
513  * @audio: the #AgsAudio
514  *
515  * Set audio.
516  *
517  * Since: 3.1.0
518  */
519 void
ags_recall_audio_set_audio(AgsRecallAudio * recall_audio,AgsAudio * audio)520 ags_recall_audio_set_audio(AgsRecallAudio *recall_audio, AgsAudio *audio)
521 {
522   if(!AGS_IS_RECALL_AUDIO(recall_audio)){
523     return;
524   }
525 
526   g_object_set(recall_audio,
527 	       "audio", audio,
528 	       NULL);
529 }
530 
531 /**
532  * ags_recall_audio_new:
533  * @audio: the assigned #AgsAudio
534  *
535  * Creates an #AgsRecallAudio.
536  *
537  * Returns: a new #AgsRecallAudio.
538  *
539  * Since: 3.0.0
540  */
541 AgsRecallAudio*
ags_recall_audio_new(AgsAudio * audio)542 ags_recall_audio_new(AgsAudio *audio)
543 {
544   AgsRecallAudio *recall_audio;
545 
546   recall_audio = (AgsRecallAudio *) g_object_new(AGS_TYPE_RECALL_AUDIO,
547 						 "audio", audio,
548 						 NULL);
549 
550   return(recall_audio);
551 }
552