Expand description
The exact solver forced alignment is built on, over any trellis of the same shape.
align is one instance of something more general.
Strip the phones out of it and what is left is T frames against N + 1
positions, where every transition consumes exactly one frame and advances the
position by a bounded number of places, and the path starts at position 0 and
ends at position N. Nothing else about the chain matters to the search.
That shape permits exact rather than pruned search. This module provides the
reachable-cell band, packed traceback, reusable score rows and a numerically
careful ⊕ in the log semiring. A Trellis implementation supplies the
transition topology and costs.
| fixed | free |
|---|---|
| one frame per transition | how many transitions there are |
| position never goes backwards | what each of them costs |
an advance of at most Trellis::REACH | what it reads, and what it means |
starts at 0, ends at N | whether the cost depends on the frame |
§Writing one
Trellis answers “what enters this cell, and what does it cost”. The cost
is the whole cost, meaning a structural penalty and whatever the frame
charges for what the transition reads, already multiplied together, so a
penalty that varies by position, by frame, or by both needs no extra
machinery.
use sicada_decode::trellis::{Step, Trellis, best_path};
/// A reference that must be sounded in order, one frame at a time, with no
/// silence and nothing skippable: the smallest trellis there is.
struct Rigid<'a> {
scores: &'a [f32],
num_symbols: usize,
phones: &'a [u32],
}
impl Trellis<2> for Rigid<'_> {
type Frame<'a> = &'a [f32] where Self: 'a;
fn num_frames(&self) -> usize {
self.scores.len() / self.num_symbols
}
fn num_positions(&self) -> usize {
self.phones.len()
}
fn frame(&self, frame: usize) -> &[f32] {
&self.scores[frame * self.num_symbols..(frame + 1) * self.num_symbols]
}
fn steps_into(&self, frame: &[f32], position: usize) -> [Step; 2] {
if position == 0 {
// Nothing reaches `s_0` after the start: the reference has to
// begin in the first frame.
return [Step::ABSENT; 2];
}
let sounding = frame[self.phones[position - 1] as usize];
// Hold this phone, or arrive at it. Listed best-first, which is the
// tie-break: a phone is held rather than started again.
[Step::new(0, sounding), Step::new(1, sounding)]
}
}
let scores = [
9.0, 0.0, 9.0, // phone 1
9.0, 0.0, 9.0, // still phone 1
9.0, 9.0, 0.0, // phone 2
];
let path = best_path(&Rigid { scores: &scores, num_symbols: 3, phones: &[1, 2] })?
.expect("the reference fits");
assert_eq!(path.positions(), [1, 1, 2]);
assert_eq!(path.codes(), [1, 0, 1]); // arrive, hold, arriveReversibleTrellis adds the same transitions read backwards, which is all
posteriors needs. It has nothing to implement, because the backward
reading is derived from the forward one. Write it out only to make the
backward pass faster, and then put axioms::check in a test: a
forward-backward over two graphs that differ does not fail, it returns
numbers that look entirely reasonable.
§What it will not do
There is no beam here and no place to put one. The band is the set of cells a complete path can stand in at all, so leaving out the rest costs nothing; narrowing it further would make the result approximate.
Modules§
- axioms
- Checks that a trellis obeys the contract the solvers rely on.
Structs§
- Path
- The best path through a trellis: which transition each frame took.
- Step
- One transition of a
Trellis: how far it moves, and what it costs. - Transition
- One transition of a trellis, as
posteriorsvisits it.
Constants§
- NEGLIGIBLE
- Where a term stops being able to change an
f32.
Traits§
- Reversible
Trellis - A
Trellisthat can also be read backwards, as a forward-backward requires. - Trellis
Tframes againstN + 1positions, one frame consumed per transition.
Functions§
- band
- The cells a complete path can stand in after
frameframes. - best_
path - The best path through
trellis, exactly. - derive_
steps_ out_ of - The transitions leaving
position, read off the ones entering the cells they could reach. - posteriors
- Forward-backward over
trellisin the log semiring: the posterior of every transition, and the total.