1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22"""
23This file defines the API that Ren'Py uses to communicate with an audio and video
24backend, and the default implementation of the API. This API is not intended
25to be stable between multiple Ren'Py releases, and so is more intended for use
26in ports to different platforms.
27
28There are a few common variables with specific datatypes here.
29
30`channel`
31    An integer giving  the number of an audio channel.  These integers are
32    allocated densely, but there's no limit to how many channels may be
33    present at one time.
34
35`file`
36    This is an object representing an open file. This may be a Python file
37    object, or a Ren'Py subfile object. All objects past to this have a
38    `name` field, giving the name of the file. SubFiles also have `base`
39    and length. Base is the offset from the start of the file where the data
40    begins, while length is the amount of data in total.
41
42Times and durations are represented as floats giving the number of seconds.
43
44A channel may have up to two files associated with it, the playing file and
45the queued file. The queued file begins playing when the current file ends
46or is stopped.
47"""
48
49# When changing this API, change webaudio.py, too!
50
51from __future__ import print_function
52
53from pygame_sdl2 cimport *
54import_pygame_sdl2()
55
56cdef extern from "renpysound_core.h":
57
58    void RPS_play(int channel, SDL_RWops *rw, char *ext, char* name, int fadein, int tight, int paused, double start, double end)
59    void RPS_queue(int channel, SDL_RWops *rw, char *ext, char *name, int fadein, int tight, double start, double end)
60    void RPS_stop(int channel)
61    void RPS_dequeue(int channel, int even_tight)
62    int RPS_queue_depth(int channel)
63    object RPS_playing_name(int channel)
64    void RPS_fadeout(int channel, int ms)
65    void RPS_pause(int channel, int pause)
66    void RPS_unpause_all_at_start()
67    int RPS_get_pos(int channel)
68    double RPS_get_duration(int channel)
69    void RPS_set_endevent(int channel, int event)
70    void RPS_set_volume(int channel, float volume)
71    float RPS_get_volume(int channel)
72    void RPS_set_pan(int channel, float pan, float delay)
73    void RPS_set_secondary_volume(int channel, float vol2, float delay)
74
75    void RPS_advance_time()
76    int RPS_video_ready(int channel)
77    object RPS_read_video(int channel)
78    void RPS_set_video(int channel, int video)
79
80    void RPS_sample_surfaces(object, object)
81    void RPS_init(int freq, int stereo, int samples, int status, int equal_mono)
82    void RPS_quit()
83
84    void RPS_periodic()
85    char *RPS_get_error()
86
87
88def check_error():
89    """
90    This is called by Ren'Py to check for an error. This function should raise
91    a meaningful exception if an error has occurred in a background thread,
92    or do nothing if an error has not occured. (It should clear any error that
93    it raises.)
94    """
95
96    e = RPS_get_error();
97    if len(e):
98        raise Exception(unicode(e, "utf-8", "replace"))
99
100def play(channel, file, name, paused=False, fadein=0, tight=False, start=0, end=0):
101    """
102    Plays `file` on `channel`. This clears the playing and queued samples and
103    replaces them with this file.
104
105    `name`
106        A python object giving a readable name for the file.
107
108    `paused`
109        If True, playback is paused rather than started.
110
111    `fadein`
112        The time it should take the fade the music in, in seconds.
113
114    `tight`
115        If true, the file is played in tight mode. This means that fadeouts
116        can span between this file and the file queued after it.
117
118    `start`
119        A time in the file to start playing.
120
121    `end`
122        A time in the file to end playing.    `
123    """
124
125    cdef SDL_RWops *rw
126
127    rw = RWopsFromPython(file)
128
129    if rw == NULL:
130        raise Exception("Could not create RWops.")
131
132    if paused:
133        pause = 1
134    else:
135        pause = 0
136
137    if tight:
138        tight = 1
139    else:
140        tight = 0
141
142    name = name.encode("utf-8")
143    RPS_play(channel, rw, name, name, fadein * 1000, tight, pause, start, end)
144    check_error()
145
146def queue(channel, file, name, fadein=0, tight=False, start=0, end=0):
147    """
148    Queues `file` on `channel` to play when the current file ends. If no file is
149    playing, plays it.
150
151    The other arguments are as for play.
152    """
153
154    cdef SDL_RWops *rw
155
156    rw = RWopsFromPython(file)
157
158    if rw == NULL:
159        raise Exception("Could not create RWops.")
160
161    if tight:
162        tight = 1
163    else:
164        tight = 0
165
166    name = name.encode("utf-8")
167    RPS_queue(channel, rw, name, name, fadein * 1000, tight, start, end)
168    check_error()
169
170def stop(channel):
171    """
172    Immediately stops `channel`, and unqueues any queued audio file.
173    """
174
175    RPS_stop(channel)
176    check_error()
177
178def dequeue(channel, even_tight=False):
179    """
180    Dequeues the queued sound file.
181
182    `even_tight`
183        If true, a queued sound file that is tight is not dequeued. If false,
184        a file marked as tight is dequeued.
185    """
186
187    RPS_dequeue(channel, even_tight)
188
189def queue_depth(channel):
190    """
191    Returns the queue depth of the channel. 0 if no file is playing, 1 if
192    a files is playing but there is no queued file, and 2 if a file is playing
193    and one is queued.
194    """
195
196    return RPS_queue_depth(channel)
197
198def playing_name(channel):
199    """
200    Returns the `name`  argument of the playing sound. This was passed into
201    `play` or `queue`.
202    """
203
204    rv = RPS_playing_name(channel)
205
206    if rv is not None:
207        rv = rv.decode("utf-8")
208
209    return rv
210
211def pause(channel):
212    """
213    Pauses `channel`.
214    """
215
216    RPS_pause(channel, 1)
217    check_error()
218
219def unpause(channel):
220    """
221    Unpauses `channel`.
222    """
223
224    RPS_pause(channel, 0)
225    check_error()
226
227def unpause_all_at_start():
228    """
229    Unpauses all channels that are paused at the start.
230    """
231
232    RPS_unpause_all_at_start()
233
234def fadeout(channel, delay):
235    """
236    Fades out `channel` over `delay` seconds.
237    """
238
239    RPS_fadeout(channel, int(delay * 1000))
240    check_error()
241
242def busy(channel):
243    """
244    Returns true if `channel` is currently playing something, and false
245    otherwise
246    """
247
248    return RPS_get_pos(channel) != -1
249
250def get_pos(channel):
251    """
252    Returns the position of the audio file playing in `channel`, in seconds.
253    Returns None if not file is is playing or it is not known.
254    """
255
256    return RPS_get_pos(channel) / 1000.0
257
258def get_duration(channel):
259    """
260    Returns the duration of the audio file playing in `channel`, in seconds, or
261    None if no file is playing or it is not known.
262    """
263
264    return RPS_get_duration(channel)
265
266def set_volume(channel, volume):
267    """
268    Sets the primary volume for `channel` to `volume`, a number between
269    0 and 1. This volume control is perceptual, taking into account the
270    logarithmic nature of human hearing.
271    """
272
273    if volume == 0:
274        RPS_set_volume(channel, 0)
275    else:
276        RPS_set_volume(channel, volume ** 2)
277
278    check_error()
279
280def set_pan(channel, pan, delay):
281    """
282    Sets the pan for channel.
283
284    `pan`
285        A number between -1 and 1 that control the placement of the audio.
286        If this is -1, then all audio is sent to the left channel.
287        If it's 0, then the two channels are equally balanced. If it's 1,
288        then all audio is sent to the right ear.
289
290    `delay`
291        The amount of time it takes for the panning to occur.
292    """
293
294    RPS_set_pan(channel, pan, delay)
295    check_error()
296
297def set_secondary_volume(channel, volume, delay):
298    """
299    Sets the secondary volume for channel. This is linear, and is multiplied
300    with the primary volume and scale factors derived from pan to find the
301    actual multiplier used on the samples.
302
303    `delay`
304        The time it takes for the change in volume to happen.
305    """
306
307    RPS_set_secondary_volume(channel, volume, delay)
308    check_error()
309
310def get_volume(channel):
311    """
312    Gets the primary volume associated with `channel`.
313    """
314
315    return RPS_get_volume(channel)
316
317def video_ready(channel):
318    """
319    Returns true if the video playing on `channel` has a frame ready for
320    presentation.
321    """
322
323    return RPS_video_ready(channel)
324
325def read_video(channel):
326    """
327    Returns the frame of video playing on `channel`. This should be returned
328    as an SDL surface with 2px of padding on all sides.
329    """
330
331    rv = RPS_read_video(channel)
332
333    if rv is None:
334        return rv
335
336    # Remove padding from the edges of the surface.
337    w, h = rv.get_size()
338
339    # This has to be set to the same number it is in ffmedia.c
340    FRAME_PADDING = 4
341    return rv.subsurface((FRAME_PADDING, FRAME_PADDING, w - FRAME_PADDING * 2, h - FRAME_PADDING * 2))
342
343# No video will be played from this channel.
344NO_VIDEO = 0
345
346# The video will be played while avoiding framedrops.
347NODROP_VIDEO = 1
348
349# The video will be played, allowing framedrops.
350DROP_VIDEO = 2
351
352def set_video(channel, video):
353    """
354    Sets a flag that determines if this channel will attempt to decode video.
355    """
356
357    if video == NODROP_VIDEO:
358        RPS_set_video(channel, NODROP_VIDEO)
359    elif video:
360        RPS_set_video(channel, DROP_VIDEO)
361    else:
362        RPS_set_video(channel, NO_VIDEO)
363
364def init(freq, stereo, samples, status=False, equal_mono=False):
365    """
366    Initializes the audio system with the given parameters. The parameter are
367    just informational - the audio system should be able to play all supported
368    files.
369
370    `freq`
371        The sample frequency to play back at.
372
373    `stereo`
374        Should we play in stereo (generally true).
375
376    `samples`
377        The length of the sample buffer.
378
379    `status`
380        If true, the sound system will print errors out to the console.
381    `
382    """
383
384    if status:
385        status = 1
386    else:
387        status = 0
388
389    RPS_init(freq, stereo, samples, status, equal_mono)
390    check_error()
391
392def quit(): # @ReservedAssignment
393    """
394    De-initializes the audio system.
395    """
396
397    RPS_quit()
398
399def periodic():
400    """
401    Called periodically (at 20 Hz).
402    """
403
404    RPS_periodic()
405
406def advance_time():
407    """
408    Called to advance time at the start of a frame.
409    """
410
411
412    RPS_advance_time()
413
414# Store the sample surfaces so they stay alive.
415rgb_surface = None
416rgba_surface = None
417
418def sample_surfaces(rgb, rgba):
419    """
420    Called to provide sample surfaces to the display system. The surfaces
421    returned by read_video should be in the same format as these.
422    """
423
424    global rgb_surface
425    global rgba_surface
426
427    rgb_surface = rgb
428    rgba_surface = rgb
429
430    RPS_sample_surfaces(rgb, rgba)
431
432# When changing this API, change webaudio.py, too!
433