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_playback.h>
21
22 #include <ags/audio/ags_channel.h>
23 #include <ags/audio/ags_output.h>
24 #include <ags/audio/ags_input.h>
25 #include <ags/audio/ags_playback_domain.h>
26 #include <ags/audio/ags_note.h>
27
28 #include <ags/audio/thread/ags_channel_thread.h>
29
30 #include <math.h>
31
32 #include <ags/i18n.h>
33
34 void ags_playback_class_init(AgsPlaybackClass *playback);
35 void ags_playback_init(AgsPlayback *playback);
36 void ags_playback_set_property(GObject *gobject,
37 guint prop_id,
38 const GValue *value,
39 GParamSpec *param_spec);
40 void ags_playback_get_property(GObject *gobject,
41 guint prop_id,
42 GValue *value,
43 GParamSpec *param_spec);
44 void ags_playback_dispose(GObject *gobject);
45 void ags_playback_finalize(GObject *gobject);
46
47 /**
48 * SECTION:ags_playback
49 * @short_description: Outputting to soundcard context
50 * @title: AgsPlayback
51 * @section_id:
52 * @include: ags/audio/ags_playback.h
53 *
54 * #AgsPlayback represents a context to output.
55 */
56
57 static gpointer ags_playback_parent_class = NULL;
58
59 enum{
60 PROP_0,
61 PROP_PLAYBACK_DOMAIN,
62 PROP_CHANNEL,
63 PROP_AUDIO_CHANNEL,
64 PROP_PLAY_NOTE,
65 };
66
67 GType
ags_playback_get_type(void)68 ags_playback_get_type (void)
69 {
70 static volatile gsize g_define_type_id__volatile = 0;
71
72 if(g_once_init_enter (&g_define_type_id__volatile)){
73 GType ags_type_playback = 0;
74
75 static const GTypeInfo ags_playback_info = {
76 sizeof(AgsPlaybackClass),
77 NULL, /* base_init */
78 NULL, /* base_finalize */
79 (GClassInitFunc) ags_playback_class_init,
80 NULL, /* class_finalize */
81 NULL, /* class_data */
82 sizeof(AgsPlayback),
83 0, /* n_preallocs */
84 (GInstanceInitFunc) ags_playback_init,
85 };
86
87 ags_type_playback = g_type_register_static(G_TYPE_OBJECT,
88 "AgsPlayback",
89 &ags_playback_info,
90 0);
91
92 g_once_init_leave(&g_define_type_id__volatile, ags_type_playback);
93 }
94
95 return g_define_type_id__volatile;
96 }
97
98 void
ags_playback_class_init(AgsPlaybackClass * playback)99 ags_playback_class_init(AgsPlaybackClass *playback)
100 {
101 GObjectClass *gobject;
102 GParamSpec *param_spec;
103
104 ags_playback_parent_class = g_type_class_peek_parent(playback);
105
106 /* GObjectClass */
107 gobject = (GObjectClass *) playback;
108
109 gobject->set_property = ags_playback_set_property;
110 gobject->get_property = ags_playback_get_property;
111
112 gobject->dispose = ags_playback_dispose;
113 gobject->finalize = ags_playback_finalize;
114
115 /* properties */
116 /**
117 * AgsPlayback:playback-domain:
118 *
119 * The parent playback domain.
120 *
121 * Since: 3.0.0
122 */
123 param_spec = g_param_spec_object("playback-domain",
124 i18n_pspec("parent playback domain"),
125 i18n_pspec("The playback domain it is child of"),
126 AGS_TYPE_PLAYBACK_DOMAIN,
127 G_PARAM_READABLE | G_PARAM_WRITABLE);
128 g_object_class_install_property(gobject,
129 PROP_PLAYBACK_DOMAIN,
130 param_spec);
131
132 /**
133 * AgsPlayback:channel:
134 *
135 * The assigned channel.
136 *
137 * Since: 3.0.0
138 */
139 param_spec = g_param_spec_object("channel",
140 i18n_pspec("assigned channel"),
141 i18n_pspec("The channel it is assigned with"),
142 AGS_TYPE_CHANNEL,
143 G_PARAM_READABLE | G_PARAM_WRITABLE);
144 g_object_class_install_property(gobject,
145 PROP_CHANNEL,
146 param_spec);
147
148 /**
149 * AgsPlayback:audio-channel:
150 *
151 * The assigned audio channel.
152 *
153 * Since: 3.0.0
154 */
155 param_spec = g_param_spec_uint("audio-channel",
156 i18n_pspec("assigned audio channel"),
157 i18n_pspec("The audio channel it is assigned with"),
158 0,
159 G_MAXUINT,
160 0,
161 G_PARAM_READABLE | G_PARAM_WRITABLE);
162 g_object_class_install_property(gobject,
163 PROP_AUDIO_CHANNEL,
164 param_spec);
165
166 /**
167 * AgsPlayback:play-note:
168 *
169 * The assigned note.
170 *
171 * Since: 3.0.0
172 */
173 param_spec = g_param_spec_object("play-note",
174 i18n_pspec("assigned note to play"),
175 i18n_pspec("The note to do playback"),
176 AGS_TYPE_NOTE,
177 G_PARAM_READABLE | G_PARAM_WRITABLE);
178 g_object_class_install_property(gobject,
179 PROP_PLAY_NOTE,
180 param_spec);
181 }
182
183 void
ags_playback_init(AgsPlayback * playback)184 ags_playback_init(AgsPlayback *playback)
185 {
186 AgsConfig *config;
187
188 gchar *thread_model, *super_threaded_scope;
189
190 gboolean super_threaded_channel;
191 guint i;
192
193 playback->flags = 0;
194
195 /* add playback mutex */
196 g_rec_mutex_init(&(playback->obj_mutex));
197
198 /* config */
199 config = ags_config_get_instance();
200
201 /* thread model */
202 super_threaded_channel = FALSE;
203
204 thread_model = ags_config_get_value(config,
205 AGS_CONFIG_THREAD,
206 "model");
207
208 if(thread_model != NULL &&
209 !g_ascii_strncasecmp(thread_model,
210 "super-threaded",
211 15)){
212 super_threaded_scope = ags_config_get_value(config,
213 AGS_CONFIG_THREAD,
214 "super-threaded-scope");
215 if(super_threaded_scope != NULL &&
216 (!g_ascii_strncasecmp(super_threaded_scope,
217 "channel",
218 8))){
219 super_threaded_channel = TRUE;
220 }
221
222 g_free(super_threaded_scope);
223 }
224
225 g_free(thread_model);
226
227 /* default flags */
228 if(super_threaded_channel){
229 playback->flags |= AGS_PLAYBACK_SUPER_THREADED_CHANNEL;
230 }
231
232 /* channel */
233 playback->channel = NULL;
234 playback->audio_channel = 0;
235
236 playback->play_note = (GObject *) ags_note_new();
237 g_object_ref(playback->play_note);
238
239 /* playback domain */
240 playback->playback_domain = NULL;
241
242 /* super threaded channel */
243 playback->channel_thread = (AgsThread **) malloc(AGS_SOUND_SCOPE_LAST * sizeof(AgsThread *));
244
245 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
246 playback->channel_thread[i] = NULL;
247 }
248
249 /* recall id */
250 playback->recall_id = (AgsRecallID **) malloc(AGS_SOUND_SCOPE_LAST * sizeof(AgsRecallID *));
251
252 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
253 playback->recall_id[i] = NULL;
254 }
255 }
256
257 void
ags_playback_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * param_spec)258 ags_playback_set_property(GObject *gobject,
259 guint prop_id,
260 const GValue *value,
261 GParamSpec *param_spec)
262 {
263 AgsPlayback *playback;
264
265 GRecMutex *playback_mutex;
266
267 playback = AGS_PLAYBACK(gobject);
268
269 /* get playback mutex */
270 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
271
272 switch(prop_id){
273 case PROP_PLAYBACK_DOMAIN:
274 {
275 GObject *playback_domain;
276
277 playback_domain = (GObject *) g_value_get_object(value);
278
279 g_rec_mutex_lock(playback_mutex);
280
281 if((GObject *) playback->playback_domain == playback_domain){
282 g_rec_mutex_unlock(playback_mutex);
283
284 return;
285 }
286
287 if(playback->playback_domain != NULL){
288 g_object_unref(G_OBJECT(playback->playback_domain));
289 }
290
291 if(playback_domain != NULL){
292 g_object_ref(G_OBJECT(playback_domain));
293 }
294
295 playback->playback_domain = (GObject *) playback_domain;
296
297 g_rec_mutex_unlock(playback_mutex);
298 }
299 break;
300 case PROP_CHANNEL:
301 {
302 GObject *channel;
303
304 channel = (GObject *) g_value_get_object(value);
305
306 g_rec_mutex_lock(playback_mutex);
307
308 if(channel == playback->channel){
309 g_rec_mutex_unlock(playback_mutex);
310
311 return;
312 }
313
314 if(playback->channel != NULL){
315 g_object_unref(G_OBJECT(playback->channel));
316 }
317
318 if(channel != NULL){
319 g_object_ref(G_OBJECT(channel));
320
321 AGS_NOTE(playback->play_note)->y = AGS_CHANNEL(channel)->pad;
322 }
323
324 playback->channel = (GObject *) channel;
325
326 g_rec_mutex_unlock(playback_mutex);
327 }
328 break;
329 case PROP_AUDIO_CHANNEL:
330 {
331 g_rec_mutex_lock(playback_mutex);
332
333 playback->audio_channel = g_value_get_uint(value);
334
335 g_rec_mutex_unlock(playback_mutex);
336 }
337 break;
338 case PROP_PLAY_NOTE:
339 {
340 GObject *note;
341
342 note = (GObject *) g_value_get_object(value);
343
344 g_rec_mutex_lock(playback_mutex);
345
346 if(note == playback->play_note){
347 g_rec_mutex_unlock(playback_mutex);
348
349 return;
350 }
351
352 if(playback->play_note != NULL){
353 g_object_unref(G_OBJECT(playback->play_note));
354 }
355
356 if(note != NULL){
357 g_object_ref(G_OBJECT(note));
358 }
359
360 playback->play_note = (GObject *) note;
361
362 g_rec_mutex_unlock(playback_mutex);
363 }
364 break;
365 default:
366 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
367 break;
368 }
369 }
370
371 void
ags_playback_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * param_spec)372 ags_playback_get_property(GObject *gobject,
373 guint prop_id,
374 GValue *value,
375 GParamSpec *param_spec)
376 {
377 AgsPlayback *playback;
378
379 GRecMutex *playback_mutex;
380
381 playback = AGS_PLAYBACK(gobject);
382
383 /* get playback mutex */
384 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
385
386 switch(prop_id){
387 case PROP_PLAYBACK_DOMAIN:
388 {
389 g_rec_mutex_lock(playback_mutex);
390
391 g_value_set_object(value,
392 playback->playback_domain);
393
394 g_rec_mutex_unlock(playback_mutex);
395 }
396 break;
397 case PROP_CHANNEL:
398 {
399 g_rec_mutex_lock(playback_mutex);
400
401 g_value_set_object(value,
402 playback->channel);
403
404 g_rec_mutex_unlock(playback_mutex);
405 }
406 break;
407 case PROP_AUDIO_CHANNEL:
408 {
409 g_rec_mutex_lock(playback_mutex);
410
411 g_value_set_uint(value,
412 playback->audio_channel);
413
414 g_rec_mutex_unlock(playback_mutex);
415 }
416 break;
417 case PROP_PLAY_NOTE:
418 {
419 g_rec_mutex_lock(playback_mutex);
420
421 g_value_set_object(value,
422 playback->play_note);
423
424 g_rec_mutex_unlock(playback_mutex);
425 }
426 break;
427 default:
428 G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, param_spec);
429 break;
430 }
431 }
432
433 void
ags_playback_dispose(GObject * gobject)434 ags_playback_dispose(GObject *gobject)
435 {
436 AgsPlayback *playback;
437
438 guint i;
439
440 playback = AGS_PLAYBACK(gobject);
441
442 /* playback domain */
443 if(playback->playback_domain != NULL){
444 ags_playback_domain_remove_playback(playback->playback_domain,
445 playback, ((AGS_IS_OUTPUT(playback->channel)) ? AGS_TYPE_OUTPUT: AGS_TYPE_INPUT));
446 }
447
448 /* channel */
449 if(playback->channel != NULL){
450 AgsChannel *channel;
451
452 channel = playback->channel;
453
454 playback->channel = NULL;
455
456 g_object_unref(channel);
457 }
458
459 /* channel thread */
460 if(playback->channel_thread != NULL){
461 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
462 if(playback->channel_thread[i] != NULL){
463 g_object_run_dispose((GObject *) playback->channel_thread[i]);
464 g_object_unref((GObject *) playback->channel_thread[i]);
465
466 playback->channel_thread[i] = NULL;
467 }
468 }
469 }
470
471 /* recall id */
472 if(playback->recall_id != NULL){
473 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
474 if(playback->recall_id[i] != NULL){
475 g_object_unref(playback->recall_id[i]);
476
477 playback->recall_id[i] = NULL;
478 }
479 }
480 }
481
482 /* call parent */
483 G_OBJECT_CLASS(ags_playback_parent_class)->dispose(gobject);
484 }
485
486 void
ags_playback_finalize(GObject * gobject)487 ags_playback_finalize(GObject *gobject)
488 {
489 AgsPlayback *playback;
490
491 guint i;
492
493 playback = AGS_PLAYBACK(gobject);
494
495 /* playback domain */
496 if(playback->playback_domain != NULL){
497 if(playback->channel != NULL){
498 ags_playback_domain_remove_playback(playback->playback_domain,
499 playback, ((AGS_IS_OUTPUT(playback->channel)) ? AGS_TYPE_OUTPUT: AGS_TYPE_INPUT));
500 }else{
501 gpointer tmp;
502
503 tmp = playback->playback_domain;
504
505 playback->playback_domain = NULL;
506
507 g_object_unref(tmp);
508 }
509 }
510
511 /* channel */
512 if(playback->channel != NULL){
513 AgsChannel *channel;
514
515 channel = playback->channel;
516
517 playback->channel = NULL;
518
519 g_object_unref(channel);
520 }
521
522 /* channel thread */
523 if(playback->channel_thread != NULL){
524 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
525 if(playback->channel_thread[i] != NULL){
526 g_object_run_dispose((GObject *) playback->channel_thread[i]);
527 g_object_unref((GObject *) playback->channel_thread[i]);
528 }
529 }
530
531 free(playback->channel_thread);
532
533 playback->channel_thread = NULL;
534 }
535
536 /* recall id */
537 if(playback->recall_id != NULL){
538 for(i = 0; i < AGS_SOUND_SCOPE_LAST; i++){
539 if(playback->recall_id[i] != NULL){
540 g_object_unref(playback->recall_id[i]);
541 }
542 }
543
544 free(playback->recall_id);
545
546 playback->recall_id = NULL;
547 }
548
549 /* call parent */
550 G_OBJECT_CLASS(ags_playback_parent_class)->finalize(gobject);
551 }
552
553 /**
554 * ags_playback_test_flags:
555 * @playback: the #AgsPlayback
556 * @flags: the flags
557 *
558 * Test @flags to be set on @playback.
559 *
560 * Returns: %TRUE if flags are set, else %FALSE
561 *
562 * Since: 3.0.0
563 */
564 gboolean
ags_playback_test_flags(AgsPlayback * playback,guint flags)565 ags_playback_test_flags(AgsPlayback *playback, guint flags)
566 {
567 gboolean retval;
568
569 GRecMutex *playback_mutex;
570
571 if(!AGS_IS_PLAYBACK(playback)){
572 return(FALSE);
573 }
574
575 /* get playback mutex */
576 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
577
578 /* test */
579 g_rec_mutex_lock(playback_mutex);
580
581 retval = (flags & (playback->flags)) ? TRUE: FALSE;
582
583 g_rec_mutex_unlock(playback_mutex);
584
585 return(retval);
586 }
587
588 /**
589 * ags_playback_set_flags:
590 * @playback: the #AgsPlayback
591 * @flags: the flags
592 *
593 * Set flags.
594 *
595 * Since: 3.0.0
596 */
597 void
ags_playback_set_flags(AgsPlayback * playback,guint flags)598 ags_playback_set_flags(AgsPlayback *playback, guint flags)
599 {
600 GRecMutex *playback_mutex;
601
602 if(!AGS_IS_PLAYBACK(playback)){
603 return;
604 }
605
606 /* get playback mutex */
607 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
608
609 /* set flags */
610 g_rec_mutex_lock(playback_mutex);
611
612 playback->flags |= flags;
613
614 g_rec_mutex_unlock(playback_mutex);
615 }
616
617 /**
618 * ags_playback_unset_flags:
619 * @playback: the #AgsPlayback
620 * @flags: the flags
621 *
622 * Unset flags.
623 *
624 * Since: 3.0.0
625 */
626 void
ags_playback_unset_flags(AgsPlayback * playback,guint flags)627 ags_playback_unset_flags(AgsPlayback *playback, guint flags)
628 {
629 GRecMutex *playback_mutex;
630
631 if(!AGS_IS_PLAYBACK(playback)){
632 return;
633 }
634
635 /* get playback mutex */
636 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
637
638 /* set flags */
639 g_rec_mutex_lock(playback_mutex);
640
641 playback->flags &= (~flags);
642
643 g_rec_mutex_unlock(playback_mutex);
644 }
645
646 /**
647 * ags_playback_set_channel_thread:
648 * @playback: the #AgsPlayback
649 * @thread: the #AgsChannelThread
650 * @sound_scope: the scope of the thread to set
651 *
652 * Set channel thread of appropriate scope.
653 *
654 * Since: 3.0.0
655 */
656 void
ags_playback_set_channel_thread(AgsPlayback * playback,AgsThread * thread,gint sound_scope)657 ags_playback_set_channel_thread(AgsPlayback *playback,
658 AgsThread *thread,
659 gint sound_scope)
660 {
661 GRecMutex *playback_mutex;
662
663 if(!AGS_IS_PLAYBACK(playback) ||
664 sound_scope >= AGS_SOUND_SCOPE_LAST){
665 return;
666 }
667
668 /* get playback mutex */
669 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
670
671 /* unset old */
672 g_rec_mutex_lock(playback_mutex);
673
674 if(playback->channel_thread[sound_scope] != NULL){
675 if(ags_thread_test_status_flags(playback->channel_thread[sound_scope], AGS_THREAD_STATUS_RUNNING)){
676 ags_thread_stop(playback->channel_thread[sound_scope]);
677 }
678
679 g_object_run_dispose((GObject *) playback->channel_thread[sound_scope]);
680 g_object_unref((GObject *) playback->channel_thread[sound_scope]);
681 }
682
683 /* set new */
684 if(thread != NULL){
685 g_object_ref(thread);
686 }
687
688 playback->channel_thread[sound_scope] = thread;
689
690 g_rec_mutex_unlock(playback_mutex);
691 }
692
693 /**
694 * ags_playback_get_channel_thread:
695 * @playback: the #AgsPlayback
696 * @sound_scope: the scope of the thread to get
697 *
698 * Get channel thread of appropriate scope.
699 *
700 * Returns: (transfer full): the matching #AgsThread or %NULL
701 *
702 * Since: 3.0.0
703 */
704 AgsThread*
ags_playback_get_channel_thread(AgsPlayback * playback,gint sound_scope)705 ags_playback_get_channel_thread(AgsPlayback *playback,
706 gint sound_scope)
707 {
708 AgsThread *channel_thread;
709
710 GRecMutex *playback_mutex;
711
712 if(!AGS_IS_PLAYBACK(playback) ||
713 sound_scope >= AGS_SOUND_SCOPE_LAST){
714 return(NULL);
715 }
716
717 /* get playback mutex */
718 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
719
720 /* get channel thread */
721 g_rec_mutex_lock(playback_mutex);
722
723 channel_thread = (playback->channel_thread != NULL) ? playback->channel_thread[sound_scope]: NULL;
724
725 if(channel_thread != NULL){
726 g_object_ref(channel_thread);
727 }
728
729 g_rec_mutex_unlock(playback_mutex);
730
731 return(channel_thread);
732 }
733
734 /**
735 * ags_playback_set_recall_id:
736 * @playback: the #AgsPlayback
737 * @recall_id: the #AgsRecallID
738 * @sound_scope: the scope of the recall id to set
739 *
740 * Set recall id of appropriate scope.
741 *
742 * Since: 3.0.0
743 */
744 void
ags_playback_set_recall_id(AgsPlayback * playback,AgsRecallID * recall_id,gint sound_scope)745 ags_playback_set_recall_id(AgsPlayback *playback,
746 AgsRecallID *recall_id,
747 gint sound_scope)
748 {
749 GRecMutex *playback_mutex;
750
751 if(!AGS_IS_PLAYBACK(playback) ||
752 sound_scope >= AGS_SOUND_SCOPE_LAST){
753 return;
754 }
755
756 /* get playback mutex */
757 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
758
759 /* unref old */
760 g_rec_mutex_lock(playback_mutex);
761
762 if(playback->recall_id[sound_scope] != NULL){
763 g_object_unref(playback->recall_id[sound_scope]);
764 }
765
766 /* ref new */
767 if(recall_id != NULL){
768 g_object_ref(recall_id);
769 }
770
771 /* set recall id */
772 playback->recall_id[sound_scope] = recall_id;
773
774 g_rec_mutex_unlock(playback_mutex);
775 }
776
777 /**
778 * ags_playback_get_recall_id:
779 * @playback: the #AgsPlayback
780 * @sound_scope: the scope of the recall id to get
781 *
782 * Get recall id of appropriate scope.
783 *
784 * Returns: (transfer full): the matching #AgsRecallID or %NULL
785 *
786 * Since: 3.0.0
787 */
788 AgsRecallID*
ags_playback_get_recall_id(AgsPlayback * playback,gint sound_scope)789 ags_playback_get_recall_id(AgsPlayback *playback,
790 gint sound_scope)
791 {
792 AgsRecallID *recall_id;
793
794 GRecMutex *playback_mutex;
795
796 if(!AGS_IS_PLAYBACK(playback) ||
797 sound_scope >= AGS_SOUND_SCOPE_LAST){
798 return(NULL);
799 }
800
801 /* get playback mutex */
802 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback);
803
804 /* get recall id */
805 g_rec_mutex_lock(playback_mutex);
806
807 if(playback->recall_id == NULL){
808 g_rec_mutex_unlock(playback_mutex);
809
810 return(NULL);
811 }
812
813 recall_id = playback->recall_id[sound_scope];
814
815 if(recall_id != NULL){
816 g_object_ref(recall_id);
817 }
818
819 g_rec_mutex_unlock(playback_mutex);
820
821 return(recall_id);
822 }
823
824 /**
825 * ags_playback_find_channel:
826 * @playback: (element-type AgsAudio.Playback) (transfer none): the #GList-struct containing #AgsPlayback
827 * @channel: the #AgsChannel
828 *
829 * Find @channel in @playback.
830 *
831 * Returns: (transfer none): the matching playback
832 *
833 * Since: 3.0.0
834 */
835 AgsPlayback*
ags_playback_find_channel(GList * playback,GObject * channel)836 ags_playback_find_channel(GList *playback,
837 GObject *channel)
838 {
839 GRecMutex *playback_mutex;
840
841 while(playback != NULL){
842 /* get playback mutex */
843 playback_mutex = AGS_PLAYBACK_GET_OBJ_MUTEX(playback->data);
844
845 /* check channel */
846 g_rec_mutex_lock(playback_mutex);
847
848 if(AGS_PLAYBACK(playback->data)->channel == channel){
849 g_rec_mutex_unlock(playback_mutex);
850
851 return(playback->data);
852 }
853
854 g_rec_mutex_unlock(playback_mutex);
855
856 /* iterate */
857 playback = playback->next;
858 }
859
860 return(NULL);
861 }
862
863 /**
864 * ags_playback_new:
865 * @channel: the #AgsChannel
866 *
867 * Instantiate a playback object and assign @channel.
868 *
869 * Returns: the new #AgsPlayback
870 *
871 * Since: 3.0.0
872 */
873 AgsPlayback*
ags_playback_new(GObject * channel)874 ags_playback_new(GObject *channel)
875 {
876 AgsPlayback *playback;
877
878 playback = (AgsPlayback *) g_object_new(AGS_TYPE_PLAYBACK,
879 "channel", channel,
880 NULL);
881
882 return(playback);
883 }
884