org.daisy.pipeline.tts.calabash.impl.SoundFileLink Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tts-common Show documentation
Show all versions of tts-common Show documentation
Common API for TTS functionality
The newest version!
package org.daisy.pipeline.tts.calabash.impl;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
import org.daisy.pipeline.audio.AudioClip;
import org.daisy.pipeline.tts.TTSLog;
/**
* Mapping between a fragment in the XML document and a fragment in an audio file.
*/
class SoundFileLink {
/** Reference to fragment in XML document */
private final URI textRef;
/** Time position where the audio fragment starts within clipBase
*/
private final Duration clipBegin;
/** Time position where the audio fragment ends within clipBase
*/
private final Duration clipEnd;
/**
* Audio clip that is used as the base for clipBegin
and clipEnd
. The
* purpose of the reference is so that it can be easily shared between {@link TextToPcmThread}
* and {@link EncodingThread} (via {@link ContiguousPCM}), because {@link AudioClip} is
* immutable.
*/
public AtomicReference clipBase;
public SoundFileLink(URI textRef, Duration clipBegin, Duration clipEnd) {
if (clipBegin == null || clipEnd == null || textRef == null)
throw new NullPointerException();
if (clipBegin.compareTo(Duration.ZERO) < 0)
throw new IllegalArgumentException("Clip begin can not be negative: " + clipBegin);
if (clipEnd.compareTo(clipBegin) < 0)
throw new IllegalArgumentException("Clip end can not come before clip begin: " + clipEnd + " < " + clipBegin);
this.textRef = textRef;
this.clipBegin = clipBegin;
this.clipEnd = clipEnd;
}
public URI getTextFragment() {
return textRef;
}
private AudioClip clip;
public AudioClip getAudioFragment(TTSLog log) {
if (clip == null) {
if (clipBase == null)
throw new IllegalStateException(); // should not happen
AudioClip base = clipBase.get();
if (base == null)
return null; // was not set in EncodingThread due to an error
Duration begin = base.clipBegin.plus(clipBegin);
Duration end = base.clipBegin.plus(clipEnd);
if (begin.compareTo(base.clipEnd) > 0) {
if (begin.minus(base.clipEnd).toMillis() > 1)
// this can not be a rounding error due to sampling
log.getOrCreateEntry(getTextFragment().toString()).addError(
new TTSLog.Error(TTSLog.ErrorCode.AUDIO_MISSING, "clip not encoded"));
begin = base.clipEnd;
end = base.clipEnd;
} else if (end.compareTo(base.clipEnd) > 0) {
if (end.minus(base.clipEnd).toMillis() > 1)
// this can not be a rounding error due to sampling
log.getOrCreateEntry(getTextFragment().toString()).addError(
new TTSLog.Error(TTSLog.ErrorCode.AUDIO_MISSING, "part of clip not encoded"));
end = base.clipEnd;
}
clip = new AudioClip(base.src, begin, end);
}
return clip;
}
}