78 lines
3.4 KiB
Plaintext
78 lines
3.4 KiB
Plaintext
// The sound of a recording, mixed: each source's packets put on the recording's timeline and added
|
|
// together, and an AAC frame encoded once every source has covered it. Shared by the Windows and
|
|
// macOS recorders (windows.jai, macos.jai), whose Recorder each has the fields used here: moved here
|
|
// from module.jai unchanged.
|
|
|
|
#scope_module
|
|
|
|
// The sound so far, from its base: one AAC frame's worth is encoded at a time.
|
|
Audio_Mix :: struct {
|
|
samples: [..] float; // interleaved stereo, from base on
|
|
base: s64; // the frame (in samples, on the timeline) samples[0] is
|
|
next: [2] s64; // each source's next frame, so packets that follow on are not moved by jitter
|
|
started: [2] bool;
|
|
frame_size: s32;
|
|
}
|
|
|
|
AUDIO_SLACK :: AUDIO_RATE / 5; // 200 ms: how late a source's packets may come and still be in the mix
|
|
AUDIO_FOLLOW_ON :: AUDIO_RATE / 33; // 30 ms: a packet that close to where its source left off follows on
|
|
|
|
// A packet into the mix at its place on the timeline, added to what the other source put there.
|
|
audio_mix_add :: (mix: *Audio_Mix, r: *Recorder, source: s64, packet: Audio_Packet) {
|
|
at := record_timeline(r, packet.ticks) * AUDIO_RATE / r.frequency;
|
|
if mix.started[source] && abs(at - mix.next[source]) <= AUDIO_FOLLOW_ON at = mix.next[source];
|
|
mix.started[source] = true;
|
|
mix.next[source] = at + packet.frames;
|
|
end := (at + packet.frames - mix.base) * AUDIO_CHANNELS;
|
|
if end > mix.samples.count {
|
|
old := mix.samples.count;
|
|
array_resize(*mix.samples, end, initialize = false);
|
|
for old..end - 1 mix.samples[it] = 0;
|
|
}
|
|
if packet.silent || !packet.samples.count return;
|
|
for frame: max(at, mix.base)..at + packet.frames - 1 {
|
|
into := (frame - mix.base) * AUDIO_CHANNELS;
|
|
from := (frame - at) * AUDIO_CHANNELS;
|
|
mix.samples[into] += packet.samples[from];
|
|
mix.samples[into + 1] += packet.samples[from + 1];
|
|
}
|
|
}
|
|
|
|
// Encodes the frames every open source has covered: up to where each has sent, or, for one that has
|
|
// sent nothing lately (silence), to AUDIO_SLACK ago. final: everything sent.
|
|
audio_mix_encode :: (mix: *Audio_Mix, r: *Recorder, final: bool) -> ok: bool, error: string {
|
|
// Paused, the timeline stands still where the pause began.
|
|
now := ifx atomic_read(*r.paused) then atomic_read(*r.pause_began) else ticks_now();
|
|
now_frame := record_timeline(r, now) * AUDIO_RATE / r.frequency;
|
|
ready := now_frame - AUDIO_SLACK;
|
|
for source, index: r.audio_sources {
|
|
if !source.open continue;
|
|
covered := max(mix.next[index], now_frame - AUDIO_SLACK);
|
|
ready = min(ready, covered);
|
|
}
|
|
if final { ready = mix.base; for mix.next ready = max(ready, it); }
|
|
|
|
size := mix.frame_size;
|
|
frame := NewArray(size * AUDIO_CHANNELS, float, initialized = false,, temp);
|
|
done: s64;
|
|
while mix.base + size <= ready {
|
|
for i: 0..frame.count - 1 {
|
|
at := done * AUDIO_CHANNELS + i;
|
|
value := ifx at < mix.samples.count then mix.samples[at] else 0.0;
|
|
frame[i] = clamp(value, -1.0, 1.0);
|
|
}
|
|
ok, error := encoder_write_audio(*r.encoder, frame.data, mix.base);
|
|
if !ok return false, error;
|
|
mix.base += size;
|
|
done += size;
|
|
lock(*r.mutex); r.stats.audio_frames += 1; unlock(*r.mutex);
|
|
}
|
|
// What was encoded leaves the front.
|
|
gone := min(done * AUDIO_CHANNELS, mix.samples.count);
|
|
if gone {
|
|
memcpy(mix.samples.data, mix.samples.data + gone, (mix.samples.count - gone) * size_of(float));
|
|
mix.samples.count -= gone;
|
|
}
|
|
return true, "";
|
|
}
|