///|
pub(all) enum AudioRegionKind {
RegionSilence
RegionQuiet
RegionSignal
RegionLoud
RegionClipping
} derive(Eq, @debug.Debug)
///|
pub fn AudioRegionKind::label(self : AudioRegionKind) -> String {
match self {
RegionSilence => "silence"
RegionQuiet => "quiet"
RegionSignal => "signal"
RegionLoud => "loud"
RegionClipping => "clipping"
}
}
///|
pub fn AudioRegionKind::is_problem(self : AudioRegionKind) -> Bool {
match self {
RegionClipping => true
_ => false
}
}
///|
pub(all) struct AudioRegion {
kind : AudioRegionKind
start_frame : Int
frame_count : Int
channels : Int
sample_rate : Int
peak : Double
rms : Double
} derive(Eq, @debug.Debug)
///|
pub fn AudioRegion::empty(kind : AudioRegionKind) -> AudioRegion {
{
kind,
start_frame: 0,
frame_count: 0,
channels: 0,
sample_rate: 0,
peak: 0.0,
rms: 0.0,
}
}
///|
pub fn AudioRegion::end_frame(self : AudioRegion) -> Int {
self.start_frame + self.frame_count
}
///|
pub fn AudioRegion::duration_seconds(self : AudioRegion) -> Double {
if self.sample_rate <= 0 {
0.0
} else {
self.frame_count.to_double() / self.sample_rate.to_double()
}
}
///|
pub fn AudioRegion::is_valid(self : AudioRegion) -> Bool {
self.start_frame >= 0 &&
self.frame_count >= 0 &&
self.channels > 0 &&
self.sample_rate > 0
}
///|
pub fn AudioRegion::contains_frame(self : AudioRegion, frame : Int) -> Bool {
frame >= self.start_frame && frame < self.end_frame()
}
///|
pub(all) struct AudioSegmentOptions {
silence_threshold : Double
quiet_threshold : Double
loud_threshold : Double
clipping_threshold : Double
min_region_frames : Int
} derive(Eq, @debug.Debug)
///|
pub fn AudioSegmentOptions::default() -> AudioSegmentOptions {
{
silence_threshold: 0.0001,
quiet_threshold: 0.02,
loud_threshold: 0.8,
clipping_threshold: 0.999,
min_region_frames: 1,
}
}
///|
pub fn AudioSegmentOptions::game_asset() -> AudioSegmentOptions {
{
silence_threshold: 0.001,
quiet_threshold: 0.03,
loud_threshold: 0.85,
clipping_threshold: 0.995,
min_region_frames: 2,
}
}
///|
pub fn AudioSegmentOptions::with_min_region_frames(
self : AudioSegmentOptions,
min_region_frames : Int,
) -> AudioSegmentOptions {
{
silence_threshold: self.silence_threshold,
quiet_threshold: self.quiet_threshold,
loud_threshold: self.loud_threshold,
clipping_threshold: self.clipping_threshold,
min_region_frames,
}
}
///|
pub(all) struct AudioSegmentation {
ok : Bool
regions : Array[AudioRegion]
source_frames : Int
channels : Int
sample_rate : Int
} derive(Eq, @debug.Debug)
///|
pub fn AudioSegmentation::empty() -> AudioSegmentation {
{ ok: false, regions: [], source_frames: 0, channels: 0, sample_rate: 0 }
}
///|
pub fn AudioSegmentation::region_count(self : AudioSegmentation) -> Int {
self.regions.length()
}
///|
pub fn AudioSegmentation::problem_count(self : AudioSegmentation) -> Int {
for region in self.regions; count = 0 {
if region.kind.is_problem() {
continue count + 1
} else {
continue count
}
} nobreak {
count
}
}
///|
pub fn AudioSegmentation::frames_for_kind(
self : AudioSegmentation,
kind : AudioRegionKind,
) -> Int {
for region in self.regions; frames = 0 {
if region.kind == kind {
continue frames + region.frame_count
} else {
continue frames
}
} nobreak {
frames
}
}
///|
pub fn AudioSegmentation::coverage_ratio(
self : AudioSegmentation,
kind : AudioRegionKind,
) -> Double {
if self.source_frames <= 0 {
0.0
} else {
self.frames_for_kind(kind).to_double() / self.source_frames.to_double()
}
}
///|
pub fn AudioSegmentation::longest_region(
self : AudioSegmentation,
) -> AudioRegion {
if self.regions.length() == 0 {
AudioRegion::empty(RegionSilence)
} else {
let mut best = self.regions[0]
for region in self.regions {
if region.frame_count > best.frame_count {
best = region
}
}
best
}
}
///|
pub fn AudioSegmentation::longest_region_of_kind(
self : AudioSegmentation,
kind : AudioRegionKind,
) -> AudioRegion {
let mut found = false
let mut best = AudioRegion::empty(kind)
for region in self.regions {
if region.kind == kind && (!found || region.frame_count > best.frame_count) {
best = region
found = true
}
}
best
}
///|
pub fn segment_audio(
buffer : FloatBuffer,
options? : AudioSegmentOptions = AudioSegmentOptions::default(),
) -> AudioSegmentation {
if !buffer.is_valid() || buffer.frame_count() == 0 {
AudioSegmentation::empty()
} else {
let safe_options = normalize_segment_options(options)
let regions : Array[AudioRegion] = []
let frames = buffer.frame_count()
let mut start_frame = 0
let mut current_kind = classify_audio_frame(buffer, 0, safe_options)
for frame in 1.. Array[AudioRegion] {
let report = segment_audio(buffer, options={
silence_threshold: threshold,
quiet_threshold: threshold * 2.0,
loud_threshold: 0.8,
clipping_threshold: 0.999,
min_region_frames,
})
filter_regions_by_kind(report.regions, RegionSilence)
}
///|
pub fn clipping_regions(
buffer : FloatBuffer,
min_region_frames? : Int = 1,
threshold? : Double = 0.999,
) -> Array[AudioRegion] {
let report = segment_audio(buffer, options={
silence_threshold: 0.0001,
quiet_threshold: 0.02,
loud_threshold: threshold * 0.8,
clipping_threshold: threshold,
min_region_frames,
})
filter_regions_by_kind(report.regions, RegionClipping)
}
///|
pub fn audio_segmentation_to_text(report : AudioSegmentation) -> String {
if !report.ok {
"MoonWavKit segmentation: empty"
} else {
let header = "MoonWavKit segmentation\nframes: \{report.source_frames}\nregions: \{report.region_count()}\nproblems: \{report.problem_count()}"
for region in report.regions; text = header {
continue "\{text}\n- \{region.kind.label()} frames=\{region.start_frame}..\{region.end_frame()} peak=\{region.peak} rms=\{region.rms}"
} nobreak {
text
}
}
}
///|
pub fn region_summary_csv(report : AudioSegmentation) -> String {
let header = "kind,start_frame,end_frame,frame_count,peak,rms"
for region in report.regions; text = header {
continue "\{text}\n\{region.kind.label()},\{region.start_frame},\{region.end_frame()},\{region.frame_count},\{region.peak},\{region.rms}"
} nobreak {
text
}
}
///|
fn normalize_segment_options(
options : AudioSegmentOptions,
) -> AudioSegmentOptions {
let silence = if options.silence_threshold < 0.0 {
0.0
} else {
options.silence_threshold
}
let quiet = if options.quiet_threshold <= silence {
silence + 0.001
} else {
options.quiet_threshold
}
let loud = if options.loud_threshold <= quiet {
quiet + 0.1
} else {
options.loud_threshold
}
let clipping = if options.clipping_threshold <= loud {
0.999
} else {
options.clipping_threshold
}
{
silence_threshold: silence,
quiet_threshold: quiet,
loud_threshold: loud,
clipping_threshold: clipping,
min_region_frames: if options.min_region_frames <= 0 {
1
} else {
options.min_region_frames
},
}
}
///|
fn classify_audio_frame(
buffer : FloatBuffer,
frame : Int,
options : AudioSegmentOptions,
) -> AudioRegionKind {
let peak = frame_peak(buffer, frame)
if peak >= options.clipping_threshold {
RegionClipping
} else if peak <= options.silence_threshold {
RegionSilence
} else if peak <= options.quiet_threshold {
RegionQuiet
} else if peak >= options.loud_threshold {
RegionLoud
} else {
RegionSignal
}
}
///|
fn frame_peak(buffer : FloatBuffer, frame : Int) -> Double {
for channel in 0.. Unit {
let frame_count = end_frame - start_frame
if frame_count >= min_region_frames {
regions.push(build_audio_region(buffer, kind, start_frame, end_frame))
}
}
///|
fn build_audio_region(
buffer : FloatBuffer,
kind : AudioRegionKind,
start_frame : Int,
end_frame : Int,
) -> AudioRegion {
let sample_start = start_frame * buffer.channels
let sample_end = end_frame * buffer.channels
let sample_count = sample_end - sample_start
if sample_count <= 0 {
AudioRegion::empty(kind)
} else {
let peak = for i in sample_start.. Array[AudioRegion] {
let out : Array[AudioRegion] = []
for region in regions {
if region.kind == kind {
out.push(region)
}
}
out
}