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