a
    V$c                  
   @   s   d dl mZmZmZmZmZ d dlmZmZm	Z	m
Z
mZmZmZmZmZmZmZmZ d dlmZ d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlm  mZ da e Z!d a"dd Z#dd Z$G d	d
 d
eZ%G dd de&Z'G dd dej(j)Z*d a+e, Z-G dd de&Z.zd dl/m0Z0 W n e1yF   Y n0 zd dl2m3Z3 W n e1yl   Y n0 g Z4i Z5d6ddZ6dd Z7dd Z8dd Z9da:da;dd Z<d d! Z=dZ>da?d"d# Z@daAdaBeC ZDd$d% ZEd&d' ZFd(d) ZGd*d+ ZHd,d- ZIdaJd.d/ ZKd0d1 ZLd2d3 ZMd4d5 ZNdS )7    )divisionabsolute_importwith_statementprint_functionunicode_literals)PY2
basestringbchrbordchropenpystrrangeroundstrtobytesunicode)raise_Nc                   C   s   t d7 a tt fS )zE
    Gets a globally unique serial number for each music change.
       )serialunique r   r   renpy/audio\audio.py
get_serial6   s    r   c              
   C   s~   zt j| }W nh t jjyx } zL|jdkrBt j|jdd n
|jdkrLtt	j
t jjdd}W Y d}~n
d}~0 0 |S )z<
    Returns a file-like object for the given filename.
    musicNvoicez_dl_silence.oggrb)renpyloaderload	webloaderDownloadNeededrtypeenqueuerelpathr   ospathjoinconfig	commondir)fnrv	exceptionr   r   r   r   @   s    

.r   c                   @   s(   e Zd ZdZdd Zdd Zdd ZdS )		AudioDataa  
    :doc: audio

    This class wraps a bytes object containing audio data, so it can be
    passed to the audio playback system. The audio data should be contained
    in some format Ren'Py supports. (For examples RIFF WAV format headers,
    not unadorned samples.)

    `data`
        A bytes object containing the audio file data.

    `filename`
        A synthetic filename associated with this data. It can be used to
        suggest the format `data` is in, and is reported as part of
        error messages.

    Once created, this can be used wherever an audio filename is allowed. For
    example::

        define audio.easteregg = AudioData(b'...', 'sample.wav')
        play sound easteregg
    c                 C   s   t | |}||_|S N)r   __new__data)clsr0   filenamer+   r   r   r   r/   j   s    zAudioData.__new__c                 C   s   d S r.   r   )selfr0   r2   r   r   r   __init__o   s    zAudioData.__init__c                 C   s   t | jt| ffS r.   )r-   r0   r   r3   r   r   r   
__reduce__r   s    zAudioData.__reduce__N)__name__
__module____qualname____doc__r/   r4   r6   r   r   r   r   r-   R   s   r-   c                   @   s   e Zd ZdZdd ZdS )
QueueEntryz
    A queue entry object.
    c                 C   s"   || _ || _|| _|| _|| _d S r.   )r2   fadeintightlooprelative_volume)r3   r2   r<   r=   r>   r?   r   r   r   r4   {   s
    zQueueEntry.__init__N)r7   r8   r9   r:   r4   r   r   r   r   r;   v   s   r;   c                       s4   e Zd ZdZdZdZdZ fddZdd Z  Z	S )	MusicContextz
    This stores information about the music in a game. This object
    participates in rollback, so when the user goes back in time, all
    the values get reverted as well.
    r   F      ?c                    sN   t t|   d | _d| _d | _d| _d| _d| _g | _	d| _
d| _d| _d S )Nr   rA   F)superr@   r4   pan_timepansecondary_volume_timesecondary_volumelast_changed
last_tightlast_filenameslast_relative_volume
force_stoppauser5   	__class__r   r   r4      s    zMusicContext.__init__c                 C   s   t  }|j| j |S )z9
        Returns a shallow copy of this context.
        )r@   __dict__updater3   r+   r   r   r   copy   s    zMusicContext.copy)
r7   r8   r9   r:   __version__rL   rJ   r4   rR   __classcell__r   r   rM   r   r@      s   "r@   c                   @   s   e Zd ZdZdd Zdd ZeeZdd ZeeZ	dd	 Z
d
d Zdd Zd1ddZdd Zdd Zdd Zd2ddZdd Zdd  Zd!d" Zd#d$ Zd%d& Zd'd( Zd)d* Zd+d, Zd-d. Zd/d0 ZdS )3ChannelzD
    This stores information about the currently-playing music.
    c
           
      C   s   || _ d | _d | _d| _d| _g | _g | _d| _d| _d| _	d| _
d | _d | _d | _|| _|| _d| _|| _|| _|| _d | _|d u rd| _d| _n|| _d| _|r|	rtjjj| _qtjjj| _ntjjj| _d S )NrA   Fr   T)name_numbermixerchan_volumeactual_volumequeuer>   playing	wait_stopsynchro_startrG   callbackrC   rE   stop_on_muter=   
keep_queuefile_prefixfile_suffixbuffer_queuepauseddefault_loopdefault_loop_setr   audio
renpysound
DROP_VIDEOmovieNODROP_VIDEONO_VIDEO)
r3   rV   rf   r`   r=   rb   rc   rd   rk   	framedropr   r   r   r4      s>    zChannel.__init__c                 C   s$   | j }|du r t }| _ td7 a|S )zk
        Returns the number of this channel, allocating a number if that
        proves necessary.
        Nr   )rW   next_channel_numberrQ   r   r   r   
get_number&  s
    
zChannel.get_numberc                 C   s4   t j j}|| j}|du r0t  }|| j< |S )z
        Returns the MusicContext corresponding to this channel, taken from
        the context object. Allocates a MusicContext if none exists.
        N)r   gamecontextr   getrV   r@   )r3   mcdr+   r   r   r   get_context6  s
    zChannel.get_contextc                 C   s&   t j j}|   }||| j< |S )z
        Copies the MusicContext associated with this channel, updates the
        ExecutionContext to point to the copy, and returns the copy.
        )r   rq   rr   r   ru   rR   rV   )r3   rt   ctxr   r   r   copy_contextF  s    
zChannel.copy_contextc                    sF  fdd  fdd} fdd}t tr<ddfS td	}|sb| j | j ddfS |d
}| j| | j }|d d}d}d}	r(d}
|
dkr| }q|
dkr| }	q|
dkr| }|	 }|r|dk rn|}q|
dkr| }q|
dkr| }	d}q d
|
q|dur<|r<|}|||	fS )zN
        Splits a filename into a filename, start time, and end time.
        c                    s   t d | S )NzParsing audio spec {!r}: {}.)	Exceptionformat)msg)r2   r   r   r,   W  s    z)Channel.split_filename.<locals>.exceptionc                     sF   s d d} z
t| W S  ty@    d| Y n0 d S )Nzexpected float at end.r   zexpected float, got {!r}.)popfloatrx   ry   vr,   specr   r   expect_floatZ  s    

z,Channel.split_filename.<locals>.expect_floatc                     sL   s d d} ztjj| W S  tyF    d| Y n0 d S )Nzexpected channel at end.r   zexpected channel, got {!r}.)r{   r   rh   get_channelrx   ry   r}   r   r   r   expect_channele  s    
z.Channel.split_filename.<locals>.expect_channelr   z
<(.*)>(.*)   r   Nfromtosyncr>   silencez_silence.oggzexpected keyword, got {!r}.)
isinstancer-   rematchrb   rc   groupsplitr{   get_posry   )r3   r2   loopedr   r   mr*   startr>   endclausesync_channeltr   )r,   r2   r   r   split_filenameR  sF    





zChannel.split_filenamec                 C   s  t jjj| jd}t jjjdd}t jjjrL| jt jjvrL|t jjj	 }| j
| | }t jjj| jdrrd}|| jkrt| j| || _| jjpt jjj| jdo| j}| jr|rt| j d| _|rd| _| jr| jt| j d | _ng | _dS d}| jdur"t| j}nd}|dkr<d| _d| _| jsHq$|dkrVq$| jsl|dkrlq$| jr~|r~q$t| jdkrq$| jd}|j | j   }d	D ]}|!|rd}q|sڐqz| "|j|j\}	}
}t jj#durt j#|	}	|dkr2||
 dkr2| jr2W qt$|jt%rPt&'|jj(}nt)|	}t*| j| j+ |dkrtj,| j||j| j|j-|j.|
||j/d
	 n$tj| j||j|j-|j.|
||j/d d| _W nR t0y   |j| jv r| j1|j qt jj2rt jj3s nY dS Y n0 q$q| js| jr| jD ]B}|dur^t4|d|j.d|j/}nt4|dddd}| j5| q:n| j6r| 6  | jj7pt8}| j9|kr|r| 7  n| :  || _9dS )z}
        This is the periodic call that causes this channel to load new stuff
        into its queues, if necessary.
        rA   mainF        Nr   r   r   )z.modz.xmz.midz.midi)re   r<   r=   r   r   r?   )r<   r=   r   r   r?   T);r   rq   preferencesvolumesrs   rX   self_voicingr(   voice_mixersself_voicing_volume_droprY   muterZ   ri   
set_volumenumberrr   rK   r`   r\   stopr]   r>   r[   lenrW   queue_depthrd   r^   r{   r2   lowerrc   endswithr   audio_filename_callbackr   r-   ioBytesIOr0   r   	set_videork   playr<   r=   r?   rx   removedebug_soundafter_rollbackr;   appendr_   rL   global_pausere   unpause)r3   mixer_volumemain_volumevolrK   topqdepthlfnir2   r   r   topfnewq
want_pauser   r   r   periodic  s    

"


 
*$



zChannel.periodicFc                 C   s   t z | jd| j | _g | _ts2W d   dS | jdu rLW d   dS | jdkrpt| j| d| _	d| _
W d   n1 s0    Y  dS )z
        Clears the queued music.

        If the first item in the queue has not been started, then it is
        left in the queue unless all is given.
        Nr   F)lockr[   ra   r>   pcm_okrW   ri   dequeuer   r]   r^   )r3   
even_tightr   r   r   r   8  s    

zChannel.dequeuec                 C   sz   d| _ trb| j| jjkr6| jj| _t| j| jjd | j| jjkrb| jj| _t	| j| jj
d | jsv| jrv|   dS )z7
        Called (mostly) once per interaction.
        r   N)ra   r   rC   rr   ri   set_panr   rD   rE   set_secondary_volumerF   r[   r_   r5   r   r   r   interactP  s    

zChannel.interactc                 C   s   t p d| _|   ts(W d   dS | jdu rBW d   dS |dkrXt| j nt| j| W d   n1 sz0    Y  dS )z
        Causes the playing music to be faded out for the given number
        of seconds. Also clears any queued music.
        r   N)	r   ra   r   r   rW   ri   r   r   fadeout)r3   secsr   r   r   r   f  s    
zChannel.fadeoutc                 C   sB   t * t| jd t| j W d   n1 s40    Y  dS )zv
        Causes this channel to be stopped in a way that looped audio will be
        reloaded and restarted.
        TN)r   ri   r   r   r   r5   r   r   r   reload|  s    zChannel.reloadTr   NrA   c                 C   s   t  |D ](}| |d\}}	}	dtjjjt|< q
|s|d u rF| j}|  jd7  _|D ]$}t	|||d|}
| j
|
 d}qX|| _|| _|rt|| _ng | _W d    n1 s0    Y  d S )NFTr   r   )r   r   r   rq   
persistent_seen_audior   r=   ra   r;   r[   r   r]   r^   listr>   )r3   	filenamesr>   r^   r<   r=   	loop_onlyr?   r2   _qer   r   r   r#     s"    zChannel.enqueuec                 C   s|   t sd S d }tX | jd ur(t| j}|d u rB| jrB| jd j}|d u rZ| jrZ| jd }W d    n1 sn0    Y  |S )Nr   )	r   r   rW   ri   playing_namer   r[   r2   r>   rQ   r   r   r   get_playing  s    
(zChannel.get_playingc                 C   s
   || _ d S r.   )rY   )r3   volumer   r   r   r     s    zChannel.set_volumec                 C   s"   t sdS | jd u rdS t| jS )Nr   )r   rW   ri   r   r   r5   r   r   r   r     s
    
zChannel.get_posc                 C   s"   t sdS | jd u rdS t| jS )Nr   )r   rW   ri   get_durationr   r5   r   r   r   r     s
    
zChannel.get_durationc                 C   s`   t H t }|| j_|| j_tr>| jj| _t| j| jj| W d    n1 sR0    Y  d S r.   )	r   r   rr   rC   rD   r   ri   r   r   )r3   rD   delaynowr   r   r   r     s    
zChannel.set_panc                 C   s`   t H t }|| j_|| j_tr>| jj| _t| j| jj| W d    n1 sR0    Y  d S r.   )	r   r   rr   rE   rF   r   ri   r   r   )r3   r   r   r   r   r   r   r     s    
zChannel.set_secondary_volumec                 C   s4   t  t| j W d    n1 s&0    Y  d S r.   )r   ri   rL   r   r5   r   r   r   rL     s    zChannel.pausec                 C   sB   | j d u rd S t t| j W d    n1 s40    Y  d S r.   )rW   r   ri   r   r   r5   r   r   r   r     s    
zChannel.unpausec                 C   s   t sd S t| jS r.   )r   ri   
read_videor   r5   r   r   r   r     s    zChannel.read_videoc                 C   s   t sdS t| jS )Nr   )r   ri   video_readyr   r5   r   r   r   r     s    zChannel.video_ready)F)TFr   NFrA   )r7   r8   r9   r:   r4   rp   propertyr   ru   rr   rw   r   r   r   r   r   r   r#   r   r   r   r   r   r   rL   r   r   r   r   r   r   r   rU      s2   ^J 




rU   )AndroidVideoChannel)IOSVideoChannelTF c                 C   s   | dkrd}|
s,t j js,d| vr,tdt jrTt jjrT| dkrTt| |||d}nBt j	r|t jjr|| dkr|t
| |||d}nt| ||||||||	d	}||_t| |t| < dS )a  
    :doc: audio
    :args: (name, mixer, loop=None, stop_on_mute=True, tight=False, file_prefix="", file_suffix="", buffer_queue=True, movie=False, framedrop=True)

    This registers a new audio channel named `name`. Audio can then be
    played on the channel by supplying the channel name to the play or
    queue statements.

    `name`
        The name of the channel.

    `mixer`
        The name of the mixer the channel uses. By default, Ren'Py knows about
        the "music", "sfx", and "voice" mixers. Using other names is possible,
        and will create the mixer if it doesn't already exist, but making new
        mixers reachable by the player requires changing the preferences screens.

    `loop`
        If true, sounds on this channel loop by default.

    `stop_on_mute`
        If true, music on the channel is stopped when the channel is muted.

    `tight`
        If true, sounds will loop even when fadeout is occurring. This should
        be set to True for a sound effects or seamless music channel, and False
        if the music fades out on its own.

    `file_prefix`
        A prefix that is prepended to the filenames of the sound files being
        played on this channel.

    `file_suffix`
        A suffix that is appended to the filenames of the sound files being
        played on this channel.

    `buffer_queue`
        Should we buffer the first second or so of a queued file? This should
        be True for audio, and False for movie playback.

    `movie`
        If true, this channel will be set up to play back videos.

    `framedrop`
        This controls what a video does when lagging. If true, frames will
        be dropped to keep up with realtime and the soundtrack. If false,
        Ren'Py will display frames late rather than dropping them.
    rk   T z-Can't register channel outside of init phase.)rf   rb   rc   )rk   rn   N)r   rq   rr   
init_phaserx   androidr(   hw_videor   iosr   rU   rX   all_channelsr   channels)rV   rX   r>   r`   r=   rb   rc   rd   rk   rn   forcecr   r   r   register_channel  s    <
r   c                 C   s(   t j jstdt| }|t|< d S )Nz*Can't alias channel outside of init phase.)r   rq   rr   r   rx   r   r   )rV   newnamer   r   r   r   alias_channelb  s    r   c                 C   s   t | d }|d u r| tjjv rXd}td| |}| s@|S tjjrL|S |d7 }q$nLd| v r| 	 d }tjj| \}}}t
| d|||d t |  S td|  |S )Nr   z{} {}r   r   F)r>   rX   rb   rc   zAudio channel %r is unknown.)r   rs   r   r(   auto_channelsr   ry   r   skippingr   r   rx   )rV   r+   r   r   baserX   rb   rc   r   r   r   r   j  s.    r   c                 C   s   |t | j_d S r.   )r   rr   rK   )rV   valuer   r   r   set_force_stop  s    r   c                  C   s  t jjrdtjv rdadad S t jrZt jjrZdd l	m
  m}  | t jjrZtj| j td u r(tr(d}t jrxd}dtjv rttjd }z"tt jjd|dt jj daW nt ty&   t jjd	 t jj  d
tjd< z"tt jjd|dt jj daW n ty    daY n0 Y n0 g }tD ]}|j|vr0||j q0d}|D ]*}t jjj|| t jjj |d qXt!. da"t#j$t%da&dt&_'t&(  W d    n1 s0    Y  d S )NRENPY_DISABLE_SOUNDFr   i   i    RENPY_SOUND_BUFSIZEr   Tz%Sound init failed. Proceeding anyway.dummySDL_AUDIODRIVERrA   )target))r   r(   soundr%   environr   mix_ok
emscriptenwebaudiorenpy.audio.webaudiorh   can_play_typeswebaudio_required_typesri   rO   rP   intinitsound_sample_rate
equal_monorx   displaylogwriter,   r   rX   r   rq   r   r   
setdefaultr   periodic_conditionperiodic_thread_quit	threadingThreadperiodic_thread_mainperiodic_threaddaemonr   )r   bufsizemixersr   default_volumer   r   r   r   r     sN    

r   c                  C   s   t d ur@t dat  W d    n1 s.0    Y  t   tsHd S tD ]B} |   | d | 	  g | _
g | _d| _d| _d| _d| _qLt  d ad ad S )NTr   F)r  r   r  notifyr'   r   r   r   r   r   r[   r>   r\   playing_midir]   r^   ri   quitr   r   r   r   r   r    s(    &
r  c                  C   s6  t sdS zg } d}tjjD ]"}t|}| | | rd}qtjjj	sNd}|r`t
s`tjj}n|snt
rnd}nd}|a
|durtD ]}|| v rq||tjj qtD ]}|  qt  d}tD ](}|jr|jrd} q|jr|jsd}q|rt  tD ]}d|_q W n  ty0   tjjr, Y n0 dS )a	  
    The periodic sound callback. This is called at around 20hz, and is
    responsible for adjusting the volume of the playing music if
    necessary, and also for calling the periodic functions of midi and
    the various channels, which then may play music.
    FTrA   N)r   r   r(   emphasize_audio_channelsr   r   r   rq   r   emphasize_audioold_emphasizedemphasize_audio_volumer   r   emphasize_audio_timer   ri   r^   r]   unpause_all_at_startrx   r   )emphasize_channels
emphasizedr   r   r   need_ssr   r   r   periodic_pass  sP    





r  c                	   C   s   t H tst d tr(W d    d S ts:W d    q daW d    n1 sR0    Y  t6 z
t  W n ty   t a	Y n0 W d    q 1 s0    Y  q d S )Ng?F)
r   run_periodicwaitr  r   r  rx   sysexc_infoperiodic_excr   r   r   r   r  c  s    
"
r  c                  C   s   t jjst  d S tV tD ]} |   qtd urRt}d at|d |d |d  da	t
  W d    n1 sr0    Y  d S )Nr   r   r   T)r   r(   audio_periodic_threadr  r   r   ru   r  r   r  r
  )r   excr   r   r   r   }  s    
r   c               
   C   s   t sdS t ztD ]} |   | j}| j|jkr4q|j}|j}| jrl|rV| 	 |vrl| 
tjjphtjj |r| j|dd|tjj|jd |j| _qW n ty   tjjr Y n0 W d   n1 s0    Y  t  dS )z/
    Called at least once per interaction.
    NTF)r>   r^   r=   r<   r?   )r   r   r   r   rr   rG   rI   rH   r>   r   r   r   r(   context_fadeout_music
fade_musicr#   context_fadein_musicrJ   rx   r   r   )r   rv   r   r=   r   r   r   r     s*    &r   c                  C   sB   t * tD ]} | js
| d q
W d   n1 s40    Y  dS )zN
    On rollback, we want to stop all the channels with non-empty sounds.
    r   N)r   r   r>   r   r  r   r   r   rollback  s    r"  c                 C   s    t D ]}|  qtj  dS )zs
    After a sound file has been changed, stop all sound (and let Ren'Py restart
    the channels, as needed.)
    N)r   r   r   exportsrestart_interaction)_fnr   r   r   r   
autoreload  s    
r&  c                   C   s   da t  dS )z&
    Pause all playback channels.
    TNr   r   r   r   r   r   	pause_all  s    r(  c                   C   s   da t  dS )z(
    Unpause all playback channels.
    FNr'  r   r   r   r   unpause_all  s    r)  c                 C   s   t sd S t | | d S r.   )ri   sample_surfaces)rgbrgbar   r   r   r*    s    r*  c                   C   s   t sd S t   d S r.   )ri   advance_timer   r   r   r   r-    s    r-  )
NNTFr   r   TFTF)O
__future__r   r   r   r   r   renpy.compatr   r   r	   r
   r   r   r   r   r   r   r   r   future.utilsr   timepygame_sdl2r%   r   r  r  r   r   renpy.audio.renpysoundrh   ri   r   r   r   r   r   r-   objectr;   
revertableRevertableObjectr@   ro   RLockr   rU   Zrenpy.audio.androidhwr   rx   Zrenpy.audio.ioshwr   r   r   r   r   r   r   r  r  r   r  
pcm_volumer  r  r  r  	Conditionr   r  r   r   r"  r&  r   r(  r)  r*  r-  r   r   r   r   <module>   s   8
$:    C          
O.B&J,