// 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, ""; }