Use internationalization header for libraries
[projects/chimara/chimara.git] / libchimara / schannel.c
1 #include <config.h>
2 #include <glib.h>
3 #include <glib/gi18n-lib.h>
4 #include <libchimara/glk.h>
5 #ifdef GSTREAMER_SOUND
6 #include <gst/gst.h>
7 #endif
8 #include "magic.h"
9 #include "schannel.h"
10 #include "chimara-glk-private.h"
11 #include "gi_dispa.h"
12 #include "gi_blorb.h"
13 #include "resource.h"
14 #include "event.h"
15
16 extern GPrivate *glk_data_key;
17
18 #ifdef GSTREAMER_SOUND
19 /* Stop any currently playing sound on this channel, and remove any
20  format-specific GStreamer elements from the channel. */
21 static void
22 clean_up_after_playing_sound(schanid_t chan)
23 {
24         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
25                 WARNING_S(_("Could not set GstElement state to"), "NULL");
26         if(chan->demux)
27         {
28                 gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
29                 chan->demux = NULL;
30         }
31         if(chan->decode)
32         {
33                 gst_bin_remove(GST_BIN(chan->pipeline), chan->decode);
34                 chan->decode = NULL;
35         }
36 }
37
38 /* This signal is thrown whenever the GStreamer pipeline generates a message.
39  Most messages are harmless. */
40 static void
41 on_pipeline_message(GstBus *bus, GstMessage *message, schanid_t s)
42 {
43         /* g_printerr("Got %s message\n", GST_MESSAGE_TYPE_NAME(message)); */
44
45         GError *err;
46         gchar *debug_message;
47         
48         switch(GST_MESSAGE_TYPE(message)) {
49         case GST_MESSAGE_ERROR: 
50         {
51                 gst_message_parse_error(message, &err, &debug_message);
52                 IO_WARNING(_("GStreamer error"), err->message, debug_message);
53                 g_error_free(err);
54                 g_free(debug_message);
55                 clean_up_after_playing_sound(s);
56         }
57                 break;
58         case GST_MESSAGE_WARNING:
59         {
60                 gst_message_parse_warning(message, &err, &debug_message);
61                 IO_WARNING(_("GStreamer warning"), err->message, debug_message);
62                 g_error_free(err);
63                 g_free(debug_message);
64         }
65                 break;
66         case GST_MESSAGE_INFO:
67         {
68                 gst_message_parse_info(message, &err, &debug_message);
69                 g_message("GStreamer info \"%s\": %s", err->message, debug_message);
70                 g_error_free(err);
71                 g_free(debug_message);
72         }
73                 break;
74         case GST_MESSAGE_EOS: /* End of stream */
75                 /* Decrease repeats if not set to forever */
76                 if(s->repeats != (glui32)-1)
77                         s->repeats--;
78                 if(s->repeats > 0) {
79                         if(!gst_element_seek_simple(s->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0)) {
80                                 WARNING(_("Could not execute GStreamer seek"));
81                                 clean_up_after_playing_sound(s);
82                         }
83                 } else {
84                         clean_up_after_playing_sound(s);
85                         /* Sound ended normally, send a notification if requested */
86                         if(s->notify)
87                                 event_throw(s->glk, evtype_SoundNotify, NULL, s->resource, s->notify);
88                 }
89                 break;
90         default:
91                 /* unhandled message */
92                 break;
93         }
94 }
95
96 /* This signal is thrown when the OGG demuxer element has decided what kind of
97  outputs it will output. We connect the decoder element dynamically. */
98 static void
99 on_ogg_demuxer_pad_added(GstElement *demux, GstPad *pad, schanid_t s)
100 {
101         GstPad *sinkpad;
102         
103         /* We can now link this pad with the vorbis-decoder sink pad */
104         sinkpad = gst_element_get_static_pad(s->decode, "sink");
105         if(gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK)
106                 WARNING(_("Could not link OGG demuxer with Vorbis decoder"));
107         gst_object_unref(sinkpad);
108 }
109
110 /* This signal is thrown when the typefinder element has found the type of its
111  input. Now that we know what kind of input stream we have, we can connect the
112  proper demuxer/decoder elements. */
113 static void
114 on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
115 {
116         gchar *type = gst_caps_to_string(caps);
117         if(strcmp(type, "application/ogg") == 0) {
118                 s->demux = gst_element_factory_make("oggdemux", NULL);
119                 s->decode = gst_element_factory_make("vorbisdec", NULL);
120                 if(!s->demux || !s->decode) {
121                         WARNING(_("Could not create one or more GStreamer elements"));
122                         goto finally;
123                 }
124                 gst_bin_add_many(GST_BIN(s->pipeline), s->demux, s->decode, NULL);
125                 if(!gst_element_link(s->typefind, s->demux) || !gst_element_link(s->decode, s->convert)) {
126                         WARNING(_("Could not link GStreamer elements"));
127                         goto finally;
128                 }
129                 /* We link the demuxer and decoder together dynamically, since the
130                  demuxer doesn't know what source pads it will have until it starts
131                  demuxing the stream */
132                 g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
133         } else if(strcmp(type, "audio/x-aiff") == 0) {
134                 s->decode = gst_element_factory_make("aiffparse", NULL);
135                 if(!s->decode) {
136                         WARNING(_("Could not create 'aiffparse' GStreamer element"));
137                         goto finally;
138                 }
139                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
140                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
141                         WARNING(_("Could not link GStreamer elements"));
142                         goto finally;
143                 }
144         } else if(strcmp(type, "audio/x-mod") == 0) {
145                 s->decode = gst_element_factory_make("modplug", NULL);
146                 if(!s->decode) {
147                         WARNING(_("Could not create 'modplug' GStreamer element"));
148                         goto finally;
149                 }
150                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
151                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
152                         WARNING(_("Could not link GStreamer elements"));
153                         goto finally;
154                 }
155         } else {
156                 WARNING_S(_("Unexpected audio type in blorb"), type);
157         }
158
159 finally:
160         g_free(type);
161 }
162 #endif /* GSTREAMER_SOUND */
163
164 /**
165  * glk_schannel_create:
166  * @rock: The rock value to give the new sound channel.
167  *
168  * This creates a sound channel, about as you'd expect.
169  *
170  * Remember that it is possible that the library will be unable to create a new
171  * channel, in which case glk_schannel_create() will return %NULL.
172  *
173  * Returns: A new sound channel, or %NULL.
174  */
175 schanid_t 
176 glk_schannel_create(glui32 rock)
177 {
178 #ifdef GSTREAMER_SOUND
179         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
180
181         schanid_t s = g_new0(struct glk_schannel_struct, 1);
182         s->magic = MAGIC_SCHANNEL;
183         s->rock = rock;
184         if(glk_data->register_obj)
185                 s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
186
187         /* Add it to the global sound channel list */
188         glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
189         s->schannel_list = glk_data->schannel_list;
190
191         /* Add a pointer to the ChimaraGlk widget, for convenience */
192         s->glk = glk_data->self;
193
194         /* Create a GStreamer pipeline for the sound channel */
195         gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
196         s->pipeline = gst_pipeline_new(pipeline_name);
197         g_free(pipeline_name);
198
199         /* Watch for messages from the pipeline */
200         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
201         gst_bus_add_signal_watch(bus);
202         g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
203         gst_object_unref(bus);
204
205         /* Create GStreamer elements to put in the pipeline */
206         s->source = gst_element_factory_make("giostreamsrc", NULL);
207         s->typefind = gst_element_factory_make("typefind", NULL);
208         s->convert = gst_element_factory_make("audioconvert", NULL);
209         s->filter = gst_element_factory_make("volume", NULL);
210         s->sink = gst_element_factory_make("autoaudiosink", NULL);
211         if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
212                 WARNING(_("Could not create one or more GStreamer elements"));
213                 goto fail;
214         }
215
216         /* Put the elements in the pipeline and link as many together as we can
217          without knowing the type of the audio stream */
218         gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
219         /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
220         if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
221                 WARNING(_("Could not link GStreamer elements"));
222                 goto fail;
223         }
224         g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
225         
226         return s;
227
228 fail:
229         glk_schannel_destroy(s);
230         return NULL;
231 #else
232         return NULL;
233 #endif /* GSTREAMER_SOUND */
234 }
235
236 /**
237  * glk_schannel_destroy:
238  * @chan: The sound channel to destroy.
239  *
240  * Destroys the channel. If the channel is playing a sound, the sound stops 
241  * immediately (with no notification event).
242  */
243 void 
244 glk_schannel_destroy(schanid_t chan)
245 {
246         VALID_SCHANNEL(chan, return);
247
248 #ifdef GSTREAMER_SOUND
249         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
250
251         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
252                 WARNING_S(_("Could not set GstElement state to"), "NULL");
253         
254         glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
255
256         if(glk_data->unregister_obj)
257         {
258                 (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
259                 chan->disprock.ptr = NULL;
260         }
261
262         /* This also frees all the objects inside the pipeline */
263         if(chan->pipeline)
264                 gst_object_unref(chan->pipeline);
265         
266         chan->magic = MAGIC_FREE;
267         g_free(chan);
268 #endif
269 }
270
271 /**
272  * glk_schannel_iterate:
273  * @chan: A sound channel, or %NULL.
274  * @rockptr: Return location for the next sound channel's rock, or %NULL.
275  *
276  * This function can be used to iterate through the list of all open channels.
277  * See <link linkend="chimara-Iterating-Through-Opaque-Objects">Iterating 
278  * Through Opaque Objects</link>.
279  *
280  * As that section describes, the order in which channels are returned is 
281  * arbitrary.
282  *
283  * Returns: the next sound channel, or %NULL if there are no more.
284  */
285 schanid_t 
286 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
287 {
288         VALID_SCHANNEL_OR_NULL(chan, return NULL);
289
290 #ifdef GSTREAMER_SOUND
291         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
292         GList *retnode;
293         
294         if(chan == NULL)
295                 retnode = glk_data->schannel_list;
296         else
297                 retnode = chan->schannel_list->next;
298         schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
299                 
300         /* Store the sound channel's rock in rockptr */
301         if(retval && rockptr)
302                 *rockptr = glk_schannel_get_rock(retval);
303                 
304         return retval;
305 #else
306         return NULL;
307 #endif /* GSTREAMER_SOUND */
308 }
309
310 /**
311  * glk_schannel_get_rock:
312  * @chan: A sound channel.
313  * 
314  * Retrieves the channel's rock value. See <link 
315  * linkend="chimara-Rocks">Rocks</link>.
316  *
317  * Returns: A rock value.
318  */
319 glui32 
320 glk_schannel_get_rock(schanid_t chan)
321 {
322         VALID_SCHANNEL(chan, return 0);
323         return chan->rock;
324 }
325
326 /**
327  * glk_schannel_play:
328  * @chan: Channel to play the sound in.
329  * @snd: Resource number of the sound to play.
330  *
331  * Begins playing the given sound on the channel. If the channel was already
332  * playing a sound (even the same one), the old sound is stopped (with no
333  * notification event.
334  *
335  * This returns 1 if the sound actually started playing, and 0 if there was any
336  * problem.
337  * <note><para>
338  *   The most obvious problem is if there is no sound resource with the given
339  *   identifier. But other problems can occur. For example, the MOD-playing 
340  *   facility in a library might be unable to handle two MODs at the same time,
341  *   in which case playing a MOD resource would fail if one was already playing.
342  * </para></note>
343  *
344  * Returns: 1 on success, 0 on failure.
345  */
346 glui32 
347 glk_schannel_play(schanid_t chan, glui32 snd)
348 {
349         return glk_schannel_play_ext(chan, snd, 1, 0);
350 }
351
352 /**
353  * glk_schannel_play_ext:
354  * @chan: Channel to play the sound in.
355  * @snd: Resource number of the sound to play.
356  * @repeats: Number of times to repeat the sound.
357  * @notify: If nonzero, requests a notification when the sound is finished.
358  *
359  * This works the same as glk_schannel_play(), but lets you specify additional 
360  * options. <code>glk_schannel_play(chan, snd)</code> is exactly equivalent to 
361  * <code>glk_schannel_play_ext(chan, snd, 1, 0)</code>.
362  * 
363  * The @repeats value is the number of times the sound should be repeated. A 
364  * repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat 
365  * forever. A repeat value of 0 means that the sound will not be played at all; 
366  * nothing happens. (Although a previous sound on the channel will be stopped, 
367  * and the function will return 1.)
368  * 
369  * The @notify value should be nonzero in order to request a sound notification
370  * event. If you do this, when the sound is completed, you will get an event 
371  * with type %evtype_SoundNotify. The @window will be %NULL, @val1 will be the 
372  * sound's resource id, and @val2 will be the nonzero value you passed as 
373  * @notify.
374  * 
375  * If you request sound notification, and the repeat value is greater than one, 
376  * you will get the event only after the last repetition. If the repeat value is
377  * 0 or -1, you will never get a notification event at all. Similarly, if the 
378  * sound is stopped or interrupted, or if the channel is destroyed while the 
379  * sound is playing, there will be no notification event.
380  *
381  * Not all libraries support sound notification. You should test the
382  * %gestalt_SoundNotify selector before you rely on it; see <link
383  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
384  * Capabilities</link>.
385  * 
386  * Returns: 1 on success, 0 on failure.
387  */
388 glui32 
389 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
390 {
391         VALID_SCHANNEL(chan, return 0);
392         g_printerr("Play sound %d with repeats %d and notify %d\n", snd, repeats, notify);
393 #ifdef GSTREAMER_SOUND
394         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
395         GInputStream *stream;
396
397         /* Stop the previous sound */
398         clean_up_after_playing_sound(chan);
399
400         /* Don't play if repeats = 0 */
401         if(repeats == 0) {
402                 chan->repeats = 0;
403                 return 1;
404         }
405
406         /* Load the sound into a GInputStream, by whatever method */
407         if(!glk_data->resource_map) {
408                 if(!glk_data->resource_load_callback) {
409                         WARNING(_("No resource map has been loaded yet."));
410                         return 0;
411                 }
412                 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
413                 if(!filename) {
414                         WARNING(_("Error loading resource from alternative location."));
415                         return 0;
416                 }
417
418                 GError *err = NULL;
419                 GFile *file = g_file_new_for_path(filename);
420                 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
421                 if(!stream) {
422                         IO_WARNING(_("Error loading resource from file"), filename, err->message);
423                         g_free(filename);
424                         g_object_unref(file);
425                         return 0;
426                 }
427                 g_free(filename);
428                 g_object_unref(file);
429         } else {
430                 giblorb_result_t resource;
431                 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
432                 if(result != giblorb_err_None) {
433                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
434                         return 0;
435                 }
436                 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
437         }
438
439         chan->repeats = repeats;
440         chan->resource = snd;
441         chan->notify = notify;
442         g_object_set(chan->source, "stream", stream, NULL);
443         g_object_unref(stream); /* Now owned by GStreamer element */
444         
445         if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
446                 WARNING_S(_("Could not set GstElement state to"), "PLAYING");
447                 return 0;
448         }
449         return 1;
450 #else
451         return 0;
452 #endif
453 }
454
455 /**
456  * glk_schannel_stop:
457  * @chan: Channel to silence.
458  *
459  * Stops any sound playing in the channel. No notification event is generated,
460  * even if you requested one. If no sound is playing, this has no effect.
461  */
462 void 
463 glk_schannel_stop(schanid_t chan)
464 {
465         VALID_SCHANNEL(chan, return);
466 #ifdef GSTREAMER_SOUND
467         clean_up_after_playing_sound(chan);
468 #endif
469 }
470
471 /**
472  * glk_schannel_set_volume:
473  * @chan: Channel to set the volume of.
474  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
475  *
476  * Sets the volume in the channel. When you create a channel, it has full 
477  * volume, represented by the value 0x10000. Half volume would be 0x8000, 
478  * three-quarters volume would be 0xC000, and so on. A volume of zero represents
479  * silence, although the sound is still considered to be playing.
480  *
481  * You can call this function between sounds, or while a sound is playing. The 
482  * effect is immediate.
483  * 
484  * You can overdrive the volume of a channel by setting a volume greater than 
485  * 0x10000. However, this is not recommended; the library may be unable to 
486  * increase the volume past full, or the sound may become distorted. You should 
487  * always create sound resources with the maximum volume you will need, and then
488  * call glk_schannel_set_volume() to reduce the volume when appropriate.
489  *
490  * Not all libraries support this function. You should test the
491  * %gestalt_SoundVolume selector before you rely on it; see <link
492  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
493  * Capabilities</link>.
494  *
495  * <note><title>Chimara</title>
496  *   <para>Chimara supports volumes from 0 to 1000&percnt;, that is, values of
497  *   @vol up to 0xA0000.</para>
498  * </note>
499  */
500 void 
501 glk_schannel_set_volume(schanid_t chan, glui32 vol)
502 {
503         VALID_SCHANNEL(chan, return);
504 #ifdef GSTREAMER_SOUND
505         gdouble volume_gst = (gdouble)vol / 0x10000;
506         g_printerr("Volume set to: %f\n", volume_gst);
507         g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
508 #endif
509 }
510
511 /**
512  * glk_sound_load_hint:
513  * @snd: Resource number of a sound.
514  * @flag: Nonzero to tell the library to load the sound, zero to tell the
515  * library to unload it.
516  *
517  * This gives the library a hint about whether the given sound should be loaded
518  * or not. If the @flag is nonzero, the library may preload the sound or do
519  * other initialization, so that glk_schannel_play() will be faster. If the
520  * @flag is zero, the library may release memory or other resources associated
521  * with the sound. Calling this function is always optional, and it has no
522  * effect on what the library actually plays.
523  */
524 void 
525 glk_sound_load_hint(glui32 snd, glui32 flag)
526 {
527 #ifdef GSTREAMER_SOUND
528         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
529         giblorb_result_t resource;
530         giblorb_err_t result;
531
532         /* Sound load hints only work for Blorb resource maps */
533         if(!glk_data->resource_map)
534                 return;
535
536         if(flag) {
537                 /* The sound load hint simply loads the resource from the resource map;
538                  loading a chunk more than once does nothing */
539                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
540                 if(result != giblorb_err_None) {
541                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
542                         return;
543                 }
544         } else {
545                 /* Get the Blorb chunk number by loading the resource with
546                  method_DontLoad, then unload that chunk - has no effect if the chunk
547                  isn't loaded */
548                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
549                 if(result != giblorb_err_None) {
550                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
551                         return;
552                 }
553                 result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
554                 if(result != giblorb_err_None) {
555                         WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
556                         return;
557                 }
558         }
559 #endif /* GSTREAMER_SOUND */
560 }