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