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", ×tamp,
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