// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
pub struct TrackPosition {
input : DynSource
samples_counted : Ref[Int]
offset_duration : Ref[@moon_cpal.Duration]
current_span_sample_rate : Ref[SampleRate]
current_span_channels : Ref[ChannelCount]
current_span_len : Ref[Int?]
}
///|
pub fn[S : Source] track_position(source : S) -> TrackPosition {
{
input: to_dyn(source),
samples_counted: @ref.new(0),
offset_duration: @ref.new(@moon_cpal.Duration::from_secs((0 : UInt64))),
current_span_sample_rate: @ref.new(0),
current_span_channels: @ref.new(0),
current_span_len: @ref.new(None),
}
}
///|
fn TrackPosition::set_current_span(self : TrackPosition) -> Unit {
self.current_span_len.val = self.input.current_span_len()
self.current_span_sample_rate.val = self.input.sample_rate()
self.current_span_channels.val = self.input.channels()
}
///|
pub fn TrackPosition::inner(self : TrackPosition) -> DynSource {
self.input
}
///|
pub fn TrackPosition::inner_mut(self : TrackPosition) -> DynSource {
self.input
}
///|
pub fn TrackPosition::into_inner(self : TrackPosition) -> DynSource {
self.input
}
///|
pub fn TrackPosition::next(self : TrackPosition) -> Sample? {
if self.current_span_len.val is None {
self.set_current_span()
}
match self.input.next() {
None => None
Some(v) => {
self.samples_counted.val += 1
if self.current_span_len.val == Some(self.samples_counted.val) {
match
duration_from_sample_count(
self.samples_counted.val,
self.current_span_channels.val,
self.current_span_sample_rate.val,
) {
Some(span_duration) =>
self.offset_duration.val = duration_add(
self.offset_duration.val,
span_duration,
)
None => ()
}
self.samples_counted.val = 0
self.set_current_span()
}
Some(v)
}
}
}
///|
pub fn TrackPosition::channels(self : TrackPosition) -> ChannelCount {
self.input.channels()
}
///|
pub fn TrackPosition::sample_rate(self : TrackPosition) -> SampleRate {
self.input.sample_rate()
}
///|
pub fn TrackPosition::get_pos(self : TrackPosition) -> @moon_cpal.Duration {
let played = match
duration_from_sample_count(
self.samples_counted.val,
self.input.channels(),
self.input.sample_rate(),
) {
Some(v) => v
None => @moon_cpal.Duration::from_secs((0 : UInt64))
}
if self.input.sample_rate() <= 0 || self.input.channels() <= 0 {
return self.offset_duration.val
}
duration_add(self.offset_duration.val, played)
}
///|
pub impl Source for TrackPosition with fn next(self : TrackPosition) {
self.next()
}
///|
pub impl Source for TrackPosition with fn channels(self : TrackPosition) {
self.channels()
}
///|
pub impl Source for TrackPosition with fn sample_rate(self : TrackPosition) {
self.sample_rate()
}
///|
pub impl Source for TrackPosition with fn current_span_len(
_self : TrackPosition,
) {
_self.input.current_span_len()
}
///|
pub impl Source for TrackPosition with fn total_duration(_self : TrackPosition) {
_self.input.total_duration()
}
///|
pub impl Source for TrackPosition with fn try_seek(
self : TrackPosition,
pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
self.input.try_seek(pos)
self.samples_counted.val = 0
self.offset_duration.val = pos
self.current_span_len.val = None
}
///|
pub struct PeriodicAccess {
input : DynSource
every_samples : Int
remaining : Ref[Int]
callback : (DynSource) -> Unit
}
///|
fn period_to_sample_count(
period : @moon_cpal.Duration,
channels : ChannelCount,
sample_rate : SampleRate,
) -> Int {
if channels <= 0 || sample_rate <= 0 {
return 1
}
let per_second = channels * sample_rate
let secs_part = period.secs.to_int() * per_second
let nanos_part = period.nanos * per_second / 1_000_000_000
let total = secs_part + nanos_part
if total <= 0 {
1
} else {
total
}
}
///|
pub fn[S : Source] periodic_access(
source : S,
every_samples : Int,
callback : () -> Unit,
) -> PeriodicAccess {
periodic_access_with_source(source, every_samples, fn(_src) { callback() })
}
///|
pub fn[S : Source] periodic_access_with_source(
source : S,
every_samples : Int,
callback : (DynSource) -> Unit,
) -> PeriodicAccess {
guard every_samples > 0 else { panic() }
{ input: to_dyn(source), every_samples, remaining: @ref.new(1), callback }
}
///|
pub fn[S : Source] periodic(
source : S,
period : @moon_cpal.Duration,
callback : () -> Unit,
) -> PeriodicAccess {
periodic_with_source(source, period, fn(_src) { callback() })
}
///|
pub fn[S : Source] periodic_with_source(
source : S,
period : @moon_cpal.Duration,
callback : (DynSource) -> Unit,
) -> PeriodicAccess {
let every = period_to_sample_count(
period,
source.channels(),
source.sample_rate(),
)
periodic_access_with_source(source, every, callback)
}
///|
pub fn PeriodicAccess::inner(self : PeriodicAccess) -> DynSource {
self.input
}
///|
pub fn PeriodicAccess::inner_mut(self : PeriodicAccess) -> DynSource {
self.input
}
///|
pub fn PeriodicAccess::into_inner(self : PeriodicAccess) -> DynSource {
self.input
}
///|
pub fn PeriodicAccess::next(self : PeriodicAccess) -> Sample? {
self.remaining.val -= 1
if self.remaining.val <= 0 {
(self.callback)(self.input)
self.remaining.val = self.every_samples
}
self.input.next()
}
///|
pub fn PeriodicAccess::channels(self : PeriodicAccess) -> ChannelCount {
self.input.channels()
}
///|
pub fn PeriodicAccess::sample_rate(self : PeriodicAccess) -> SampleRate {
self.input.sample_rate()
}
///|
pub impl Source for PeriodicAccess with fn next(self : PeriodicAccess) {
self.next()
}
///|
pub impl Source for PeriodicAccess with fn channels(self : PeriodicAccess) {
self.channels()
}
///|
pub impl Source for PeriodicAccess with fn sample_rate(self : PeriodicAccess) {
self.sample_rate()
}
///|
pub impl Source for PeriodicAccess with fn current_span_len(
_self : PeriodicAccess,
) {
_self.input.current_span_len()
}
///|
pub impl Source for PeriodicAccess with fn total_duration(
_self : PeriodicAccess,
) {
_self.input.total_duration()
}
///|
pub impl Source for PeriodicAccess with fn try_seek(
self : PeriodicAccess,
pos : @moon_cpal.Duration,
) -> Unit raise SeekError {
self.input.try_seek(pos)
}