luv

Workshop wiki

sound.lisp

luvcraft/sound.lisp

system luvcraft/core · 8 definitions · on GitHub

A film's sound: decoded on its own thread, heard from where the player stands, and the clock the picture keeps time by.

The pump is one thread per playing film. It keeps a quarter second of sound queued in an SDL audio sink and otherwise sleeps; every push places the mono signal between the two channels according to the pan the render thread last computed, and the sink's own gain carries the distance. Two threads share three numbers -- gain, left, right -- each written whole and read whole, which is all the synchronisation a float needs.

The clock: frames the sink has actually played, minus the frame count at the last rewind, over the rate. That is the film's time as the ear hears it, and advance-video-screen shows the picture that is due at it.

in-package#:luvcraft
defparameter*film-sound-lead*0.25

How many seconds of sound the pump keeps queued ahead of the speaker.

Enough that a busy frame or a garbage collection does not run the queue dry, little enough that a pan or a gain change is heard within a blink.

defparameter*film-sound-reference-distance*4.0

Cells from the screen at which the film is heard at full volume; beyond it the sound falls off with the square of the distance.

defclassfilm-sound
track:initarg:track:readerfilm-sound-track
sink:initarg:sink:readerfilm-sound-sink
thread:initformnil:accessorfilm-sound-thread
running-p:initformt:accessorfilm-sound-running-p
loop-p:initarg:loop-p:initformt:readerfilm-sound-loop-p
finished-p:initformnil:accessorfilm-sound-finished-p:documentation

True once a film that does not loop has all been handed to the sink.

The sink's frame count when the current pass through the film began.

loop-start:initform0:accessorfilm-sound-loop-start
passes:initform0:accessorfilm-sound-passes:documentation"How many times the film has started over."

Written by the render thread, read by the pump.

gain:initform1.0:accessorfilm-sound-gain
left:initform1.0:accessorfilm-sound-left
right:initform1.0:accessorfilm-sound-right
buffer:initformnil:accessorfilm-sound-buffer
:documentation

One film's soundtrack, playing.

defunopen-film-sound
pathname&key
loop-pt

Open pathname's soundtrack into a sink and start pumping it, or return NIL for a silent film.

let
track
whentrack
let
sinknil
soundnil
handler-case
progn
setfsink
luv:open-audio-sink:rate
libav:audio-sample-ratetrack
:channels2
sound
make-instance'film-sound:tracktrack:sinksink:loop-ploop-p
setf
film-sound-threadsound
sb-thread:make-thread
lambda
:name
formatnil"film sound ~A"
file-namestringpathname
sound
error
condition
warn"The film's sound could not start; playing silent: ~A"condition
nil
defunrun-film-sound
sound

The pump: keep the sink fed until told to stop.

let
track
film-sound-tracksound
sink
film-sound-sinksound
handler-case
loopwhile
film-sound-running-psound
do
cond
film-sound-finished-psound
sleep0.05
multiple-value-bind
samplescount
libav:audio-frame-mono-samplestrack
film-sound-buffersound
setf
film-sound-buffersound
samples
luv:push-audio-sink-monosinksamplescount:left
film-sound-leftsound
:right
film-sound-rightsound
film-sound-loop-psound
setf
film-sound-loop-startsound
luv:audio-sink-frames-pushedsink
incf
film-sound-passessound
t
setf
film-sound-finished-psound
t
error
condition
luv:log-event:luvcraft"film sound stopped: ~A"condition
defunclose-film-sound
sound

Stop the pump, then release the sink and the decoder.

setf
film-sound-running-psound
nil
alexandria:when-let
thread
film-sound-threadsound
when
and
sb-thread:thread-alive-pthread
not
eqthreadsb-thread:*current-thread*
sb-thread:join-threadthread:defaultnil:timeout2
setf
film-sound-threadsound
nil
luv:close-audio-sink
film-sound-sinksound
libav:close-audio
film-sound-tracksound
sound
defunfilm-sound-time
sound

Seconds into the current pass of the film, as heard.

Negative for the moment after a rewind while the previous pass's tail is still coming out of the speaker: the picture should hold its last frame until this comes round to zero.

/
-
luv:audio-sink-played-frames
film-sound-sinksound
film-sound-loop-startsound
float
luv:audio-sink-rate
film-sound-sinksound
1.0
defunplace-film-sound-listener
soundsourcelistenerright

Hear sound, which comes from world point source, at listener facing with right as the ear-to-ear axis: set the gain by distance and the pan by bearing.

Distance follows an inverse square past the reference distance -- a screen in the next field is a murmur, at the wall it is the film. Pan is constant power over the bearing's sine, so walking past the screen swings it from one ear to the other without a dip in the middle.

let*
dx
-
vec3-xsource
vec3-xlistener
dy
-
vec3-ysource
vec3-ylistener
dz
-
vec3-zsource
vec3-zlistener
distance
sqrt
+
*dxdx
*dydy
*dzdz
gain
if
<=distancereference
1.0
/
*referencereference

The bearing's sine: how far to the right the source lies, from -1 (hard left) to 1 (hard right). A source at the listener has no bearing and sits in the middle.

side
if0.0
/
+
*dx
vec3-xright
*dy
vec3-yright
*dz
vec3-zright
distance
setf
film-sound-gainsound
coercegain'single-float
film-sound-leftsound
coerce'single-float
film-sound-rightsound
coerce'single-float
luv:audio-sink-gain
film-sound-sinksound
gain
sound