[rustpotterks] Upgrade to version 3 (#15556)
* Upgrade to version 3 * Use ThreadPoolManager and add sleep * Remove pool prefix, already added by thread pool manager Signed-off-by: Miguel Álvarez <miguelwork92@gmail.com>pull/15681/head
parent
5a39985420
commit
1abb8f267e
|
@ -3,12 +3,15 @@
|
||||||
This voice service allows you to use the open source library Rustpotter as your keyword spotter in openHAB.
|
This voice service allows you to use the open source library Rustpotter as your keyword spotter in openHAB.
|
||||||
[Rustpotter](https://github.com/GiviMAD/rustpotter) is a free and open-source keywords spotter written in rust.
|
[Rustpotter](https://github.com/GiviMAD/rustpotter) is a free and open-source keywords spotter written in rust.
|
||||||
|
|
||||||
Rustpotter provides personal on-device wake word detection. You need to generate a model for your keyword using audio samples.
|
Rustpotter provides personal on-device wake word detection.
|
||||||
|
You need to generate a file for your keyword using audio samples.
|
||||||
|
|
||||||
You can test library in your browser using these web pages:
|
You can test the library in your browser using these web pages:
|
||||||
|
|
||||||
- [The spot demo](https://givimad.github.io/rustpotter-worklet-demo/), which include some example wakewords (but it's recommended to use your own).
|
- [The spot demo](https://givimad.github.io/rustpotter-worklet-demo/), which include some example wakewords (but it's recommended to use your own).
|
||||||
- [The model creation demo](https://givimad.github.io/rustpotter-create-model-demo/), it allows you to record compatible wav files and generate a wakeword file that you can test on the previous page.
|
- [The wakeword reference creation demo](https://givimad.github.io/rustpotter-create-model-demo/), it allows you to record compatible wav files and generate a wakeword reference files that you can test on the previous page.
|
||||||
|
|
||||||
|
There is also this [command line utility](https://github.com/GiviMAD/rustpotter-cli) that allows taking records and testing the library.
|
||||||
|
|
||||||
Important: No voice data listened by this service will be uploaded to the Cloud.
|
Important: No voice data listened by this service will be uploaded to the Cloud.
|
||||||
The voice data is processed offline, locally on your openHAB server by Rustpotter.
|
The voice data is processed offline, locally on your openHAB server by Rustpotter.
|
||||||
|
@ -17,12 +20,14 @@ The voice data is processed offline, locally on your openHAB server by Rustpotte
|
||||||
|
|
||||||
After installing, you will be able to access the service options through the openHAB configuration page in UI (**Settings / Other Services - Rustpotter Keyword Spotter**) to edit them:
|
After installing, you will be able to access the service options through the openHAB configuration page in UI (**Settings / Other Services - Rustpotter Keyword Spotter**) to edit them:
|
||||||
|
|
||||||
- **Threshold** - Configures the detector threshold, is the min score (in range 0. to 1.) that some wake word template should obtain to trigger a detection. Defaults to 0.5.
|
- **Threshold** - Configures the detector threshold, is the min score (in range 0. to 1.) to trigger the detection. Defaults to 0.5.
|
||||||
- **Averaged Threshold** - Configures the detector averaged threshold, is the min score (in range 0. to 1.) that the audio should obtain against a combination of the wake word templates, the detection will be aborted if this is not the case. This way it can prevent to run the comparison of the current frame against each of the wake word templates which saves cpu. If set to 0 this functionality is disabled.
|
- **Averaged Threshold** - Configures the detector averaged threshold. If set to 0 this functionality is disabled.
|
||||||
- **Score Mode** - Indicates how to calculate the final score.
|
- **Score Mode** - Indicates how to calculate the final score. (Only applies to wakeword references)
|
||||||
- **Min Scores** - Minimum number of positive scores to consider a partial detection as a detection.
|
- **Min Scores** - Minimum number of positive scores required to not discard the detection.
|
||||||
- **Comparator Ref** - Configures the reference for the comparator used to match the samples.
|
- **Eager** - Emit detection on min partial scores.
|
||||||
- **Comparator Band Size** - Configures the band-size for the comparator used to match the samples.
|
- **VAD Mode** - Enables a basic vad detector to discard some execution.
|
||||||
|
- **Score Ref** - Configures the reference for the comparator used to match the samples.
|
||||||
|
- **Band Size** - Configures the band-size for the comparator used to match the samples. (Only applies to wakeword references)
|
||||||
- **Gain Normalizer** - Enables an audio filter that intent to approximate the volume of the stream to a reference level.
|
- **Gain Normalizer** - Enables an audio filter that intent to approximate the volume of the stream to a reference level.
|
||||||
- **Min Gain** - Min gain applied by the gain normalizer filter.
|
- **Min Gain** - Min gain applied by the gain normalizer filter.
|
||||||
- **Max Gain** - Max gain applied by the gain normalizer filter.
|
- **Max Gain** - Max gain applied by the gain normalizer filter.
|
||||||
|
@ -40,26 +45,23 @@ org.openhab.voice.rustpotterks:threshold=0.5
|
||||||
org.openhab.voice.rustpotterks:averagedthreshold=0.2
|
org.openhab.voice.rustpotterks:averagedthreshold=0.2
|
||||||
org.openhab.voice.rustpotterks:scoreMode=max
|
org.openhab.voice.rustpotterks:scoreMode=max
|
||||||
org.openhab.voice.rustpotterks:minScores=5
|
org.openhab.voice.rustpotterks:minScores=5
|
||||||
org.openhab.voice.rustpotterks:comparatorRef=0.22
|
org.openhab.voice.rustpotterks:scoreRef=0.22
|
||||||
org.openhab.voice.rustpotterks:comparatorBandSize=5
|
org.openhab.voice.rustpotterks:bandSize=5
|
||||||
org.openhab.voice.rustpotterks:gainNormalizer=true
|
org.openhab.voice.rustpotterks:gainNormalizer=true
|
||||||
org.openhab.voice.rustpotterks:minGain=0.5
|
org.openhab.voice.rustpotterks:minGain=0.5
|
||||||
org.openhab.voice.rustpotterks:maxGain=1
|
org.openhab.voice.rustpotterks:maxGain=1
|
||||||
org.openhab.voice.rustpotterks:gainRef=
|
org.openhab.voice.rustpotterks:gainRef=0.004
|
||||||
org.openhab.voice.rustpotterks:bandPass=true
|
|
||||||
org.openhab.voice.rustpotterks:lowCutoff=80
|
|
||||||
org.openhab.voice.rustpotterks:highCutoff=400
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Magic Word Configuration
|
## Magic Word Configuration
|
||||||
|
|
||||||
The magic word to spot is gathered from your 'Voice' configuration.
|
The magic word to spot is gathered from your 'Voice' configuration.
|
||||||
|
|
||||||
You can generate your own wakeword files using the [Rustpotter CLI](https://github.com/GiviMAD/rustpotter-cli).
|
You can generate your own wakeword files using the [command line utility](https://github.com/GiviMAD/rustpotter-cli).
|
||||||
|
|
||||||
You can also download the models used as examples on the [rustpotter web demo](https://givimad.github.io/rustpotter-worklet-demo/) from [this folder](https://github.com/GiviMAD/rustpotter-worklet-demo/tree/main/static).
|
You can also download the wakeword used as examples on the [rustpotter web demo](https://givimad.github.io/rustpotter-worklet-demo/) from [this folder](https://github.com/GiviMAD/rustpotter-worklet-demo/tree/main/static).
|
||||||
|
|
||||||
To use a wake word model, you should place the file under '\<openHAB userdata\>/rustpotter' and configure your magic word to match the file name replacing spaces with '_' and adding the extension '.rpw'.
|
To use a wake word wakeword, you should place the file under '\<openHAB userdata\>/rustpotter' and configure your magic word to match the file name replacing spaces with '_' and adding the extension '.rpw'.
|
||||||
As an example, the file generated for the keyword "ok openhab" will be named 'ok_openhab.rpw'.
|
As an example, the file generated for the keyword "ok openhab" will be named 'ok_openhab.rpw'.
|
||||||
|
|
||||||
The service will only work if it's able to find the correct rpw for your magic word configuration.
|
The service will only work if it's able to find the correct rpw for your magic word configuration.
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.givimad</groupId>
|
<groupId>io.github.givimad</groupId>
|
||||||
<artifactId>rustpotter-java</artifactId>
|
<artifactId>rustpotter-java</artifactId>
|
||||||
<version>2.0.1</version>
|
<version>3.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -23,35 +23,46 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class RustpotterKSConfiguration {
|
public class RustpotterKSConfiguration {
|
||||||
/**
|
/**
|
||||||
* Configures the detector threshold, is the min score (in range 0. to 1.) that some wake word template should
|
* Configures the detector threshold, is the min score (in range 0. to 1.) to trigger the detection.
|
||||||
* obtain to trigger a detection. Defaults to 0.5.
|
* Defaults to 0.5.
|
||||||
*/
|
*/
|
||||||
public float threshold = 0.5f;
|
public float threshold = 0.5f;
|
||||||
/**
|
/**
|
||||||
* Configures the detector averaged threshold, is the min score (in range 0. to 1.) that the audio should obtain
|
* Configures the detector averaged threshold.
|
||||||
* against a
|
|
||||||
* combination of the wake word templates, the detection will be aborted if this is not the case. This way it can
|
|
||||||
* prevent to
|
|
||||||
* run the comparison of the current frame against each of the wake word templates which saves cpu.
|
|
||||||
* If set to 0 this functionality is disabled.
|
* If set to 0 this functionality is disabled.
|
||||||
*/
|
*/
|
||||||
public float averagedThreshold = 0.2f;
|
public float averagedThreshold = 0f;
|
||||||
/**
|
/**
|
||||||
* Indicates how to calculate the final score.
|
* Indicates how to calculate the final score.
|
||||||
|
* Only applies to not trained wakewords.
|
||||||
*/
|
*/
|
||||||
public String scoreMode = "max";
|
public String scoreMode = "max";
|
||||||
/**
|
/**
|
||||||
* Minimum number of positive scores to consider a partial detection as a detection.
|
* Enables a basic vad detector to discard some execution.
|
||||||
|
*/
|
||||||
|
public String vadMode = "";
|
||||||
|
/**
|
||||||
|
* Minimum number of positive scores required to not discard the detection.
|
||||||
*/
|
*/
|
||||||
public int minScores = 5;
|
public int minScores = 5;
|
||||||
|
/**
|
||||||
|
* Emit detection on min partial scores.
|
||||||
|
*/
|
||||||
|
public boolean eager = false;
|
||||||
/**
|
/**
|
||||||
* Configures the reference for the comparator used to match the samples.
|
* Configures the reference for the comparator used to match the samples.
|
||||||
*/
|
*/
|
||||||
public float comparatorRef = 0.22f;
|
public float scoreRef = 0.22f;
|
||||||
/**
|
/**
|
||||||
* Configures the band-size for the comparator used to match the samples.
|
* Configures the band-size for the comparator used to match the samples.
|
||||||
|
* Only applies to wakeword references.
|
||||||
*/
|
*/
|
||||||
public int comparatorBandSize = 5;
|
public int bandSize = 5;
|
||||||
|
/**
|
||||||
|
* Create wav record on the first partial detections and any other one that surpasses its score.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean record = false;
|
||||||
/**
|
/**
|
||||||
* Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS of the
|
* Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS of the
|
||||||
* samples is used as volume measure).
|
* samples is used as volume measure).
|
||||||
|
|
|
@ -14,14 +14,16 @@ package org.openhab.voice.rustpotterks.internal;
|
||||||
|
|
||||||
import static org.openhab.voice.rustpotterks.internal.RustpotterKSConstants.*;
|
import static org.openhab.voice.rustpotterks.internal.RustpotterKSConstants.*;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
@ -47,9 +49,11 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import io.github.givimad.rustpotter_java.Endianness;
|
import io.github.givimad.rustpotter_java.Endianness;
|
||||||
import io.github.givimad.rustpotter_java.Rustpotter;
|
import io.github.givimad.rustpotter_java.Rustpotter;
|
||||||
import io.github.givimad.rustpotter_java.RustpotterBuilder;
|
import io.github.givimad.rustpotter_java.RustpotterConfig;
|
||||||
|
import io.github.givimad.rustpotter_java.RustpotterDetection;
|
||||||
import io.github.givimad.rustpotter_java.SampleFormat;
|
import io.github.givimad.rustpotter_java.SampleFormat;
|
||||||
import io.github.givimad.rustpotter_java.ScoreMode;
|
import io.github.givimad.rustpotter_java.ScoreMode;
|
||||||
|
import io.github.givimad.rustpotter_java.VADMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link RustpotterKSService} is a keyword spotting implementation based on rustpotter.
|
* The {@link RustpotterKSService} is a keyword spotting implementation based on rustpotter.
|
||||||
|
@ -61,28 +65,30 @@ import io.github.givimad.rustpotter_java.ScoreMode;
|
||||||
@ConfigurableService(category = SERVICE_CATEGORY, label = SERVICE_NAME
|
@ConfigurableService(category = SERVICE_CATEGORY, label = SERVICE_NAME
|
||||||
+ " Keyword Spotter", description_uri = SERVICE_CATEGORY + ":" + SERVICE_ID)
|
+ " Keyword Spotter", description_uri = SERVICE_CATEGORY + ":" + SERVICE_ID)
|
||||||
public class RustpotterKSService implements KSService {
|
public class RustpotterKSService implements KSService {
|
||||||
private static final String RUSTPOTTER_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "rustpotter").toString();
|
private static final Path RUSTPOTTER_FOLDER = Path.of(OpenHAB.getUserDataFolder(), "rustpotter");
|
||||||
|
private static final Path RUSTPOTTER_RECORDS_FOLDER = RUSTPOTTER_FOLDER.resolve("records");
|
||||||
private final Logger logger = LoggerFactory.getLogger(RustpotterKSService.class);
|
private final Logger logger = LoggerFactory.getLogger(RustpotterKSService.class);
|
||||||
private final ScheduledExecutorService executor = ThreadPoolManager.getScheduledPool("OH-voice-rustpotterks");
|
private final ExecutorService executor = ThreadPoolManager.getPool("voice-rustpotterks");
|
||||||
private RustpotterKSConfiguration config = new RustpotterKSConfiguration();
|
private RustpotterKSConfiguration config = new RustpotterKSConfiguration();
|
||||||
static {
|
private final List<RustpotterMutex> runningInstances = new ArrayList<>();
|
||||||
Logger logger = LoggerFactory.getLogger(RustpotterKSService.class);
|
|
||||||
File directory = new File(RUSTPOTTER_FOLDER);
|
|
||||||
if (!directory.exists()) {
|
|
||||||
if (directory.mkdir()) {
|
|
||||||
logger.info("rustpotter dir created {}", RUSTPOTTER_FOLDER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
protected void activate(Map<String, Object> config) {
|
protected void activate(Map<String, Object> config) {
|
||||||
|
logger.debug("Loading library");
|
||||||
|
tryCreateDir(RUSTPOTTER_FOLDER);
|
||||||
|
tryCreateDir(RUSTPOTTER_RECORDS_FOLDER);
|
||||||
|
try {
|
||||||
|
Rustpotter.loadLibrary();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Unable to load rustpotter native library: {}", e.getMessage());
|
||||||
|
}
|
||||||
modified(config);
|
modified(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Modified
|
@Modified
|
||||||
protected void modified(Map<String, Object> config) {
|
protected void modified(Map<String, Object> config) {
|
||||||
this.config = new Configuration(config).as(RustpotterKSConfiguration.class);
|
this.config = new Configuration(config).as(RustpotterKSConfiguration.class);
|
||||||
|
asyncUpdateActiveInstances();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -102,19 +108,15 @@ public class RustpotterKSService implements KSService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<AudioFormat> getSupportedFormats() {
|
public Set<AudioFormat> getSupportedFormats() {
|
||||||
return Set
|
return Set.of(
|
||||||
.of(new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, null, null, null));
|
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, null, 16000L),
|
||||||
|
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, 16, null, null),
|
||||||
|
new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null, 32, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KSServiceHandle spot(KSListener ksListener, AudioStream audioStream, Locale locale, String keyword)
|
public KSServiceHandle spot(KSListener ksListener, AudioStream audioStream, Locale locale, String keyword)
|
||||||
throws KSException {
|
throws KSException {
|
||||||
logger.debug("Loading library");
|
|
||||||
try {
|
|
||||||
Rustpotter.loadLibrary();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new KSException("Unable to load rustpotter lib: " + e.getMessage());
|
|
||||||
}
|
|
||||||
var audioFormat = audioStream.getFormat();
|
var audioFormat = audioStream.getFormat();
|
||||||
var frequency = audioFormat.getFrequency();
|
var frequency = audioFormat.getFrequency();
|
||||||
var bitDepth = audioFormat.getBitDepth();
|
var bitDepth = audioFormat.getBitDepth();
|
||||||
|
@ -125,27 +127,35 @@ public class RustpotterKSService implements KSService {
|
||||||
"Missing stream metadata: frequency, bit depth, channels and endianness must be defined.");
|
"Missing stream metadata: frequency, bit depth, channels and endianness must be defined.");
|
||||||
}
|
}
|
||||||
var endianness = isBigEndian ? Endianness.BIG : Endianness.LITTLE;
|
var endianness = isBigEndian ? Endianness.BIG : Endianness.LITTLE;
|
||||||
logger.debug("Audio wav spec: frequency '{}', bit depth '{}', channels '{}', '{}'", frequency, bitDepth,
|
logger.debug("Audio wav spec: sample rate {}, {} bits, {} channels, {}", frequency, bitDepth, channels,
|
||||||
channels, isBigEndian ? "big-endian" : "little-endian");
|
isBigEndian ? "big-endian" : "little-endian");
|
||||||
|
var wakewordName = keyword.replaceAll("\\s", "_") + ".rpw";
|
||||||
|
|
||||||
|
var wakewordPath = RUSTPOTTER_FOLDER.resolve(wakewordName);
|
||||||
|
if (!Files.exists(wakewordPath)) {
|
||||||
|
throw new KSException("Missing wakeword file: " + wakewordPath);
|
||||||
|
}
|
||||||
Rustpotter rustpotter;
|
Rustpotter rustpotter;
|
||||||
try {
|
try {
|
||||||
rustpotter = initRustpotter(frequency, bitDepth, channels, endianness);
|
rustpotter = initRustpotter(frequency, bitDepth, channels, endianness);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new KSException("Unable to configure rustpotter: " + e.getMessage(), e);
|
throw new KSException("Unable to start rustpotter: " + e.getMessage(), e);
|
||||||
}
|
|
||||||
var modelName = keyword.replaceAll("\\s", "_") + ".rpw";
|
|
||||||
var modelPath = Path.of(RUSTPOTTER_FOLDER, modelName);
|
|
||||||
if (!modelPath.toFile().exists()) {
|
|
||||||
throw new KSException("Missing model " + modelName);
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
rustpotter.addWakewordModelFile(modelPath.toString());
|
rustpotter.addWakewordFile("w", wakewordPath.toString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new KSException("Unable to load wake word model: " + e.getMessage());
|
throw new KSException("Unable to load wakeword file: " + e.getMessage());
|
||||||
}
|
}
|
||||||
logger.debug("Model '{}' loaded", modelPath);
|
logger.debug("Wakeword '{}' loaded", wakewordPath);
|
||||||
AtomicBoolean aborted = new AtomicBoolean(false);
|
AtomicBoolean aborted = new AtomicBoolean(false);
|
||||||
executor.submit(() -> processAudioStream(rustpotter, ksListener, audioStream, aborted));
|
int bufferSize = (int) rustpotter.getBytesPerFrame();
|
||||||
|
long bytesPerMs = frequency / 1000 * (long) bitDepth;
|
||||||
|
RustpotterMutex rustpotterMutex = new RustpotterMutex(rustpotter);
|
||||||
|
synchronized (this.runningInstances) {
|
||||||
|
this.runningInstances.add(rustpotterMutex);
|
||||||
|
}
|
||||||
|
executor.submit(
|
||||||
|
() -> processAudioStream(rustpotterMutex, bufferSize, bytesPerMs, ksListener, audioStream, aborted));
|
||||||
return () -> {
|
return () -> {
|
||||||
logger.debug("Stopping service");
|
logger.debug("Stopping service");
|
||||||
aborted.set(true);
|
aborted.set(true);
|
||||||
|
@ -154,40 +164,48 @@ public class RustpotterKSService implements KSService {
|
||||||
|
|
||||||
private Rustpotter initRustpotter(long frequency, int bitDepth, int channels, Endianness endianness)
|
private Rustpotter initRustpotter(long frequency, int bitDepth, int channels, Endianness endianness)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
var rustpotterBuilder = new RustpotterBuilder();
|
var rustpotterConfig = initRustpotterConfig();
|
||||||
// audio configs
|
// audio format config just need to be set for initializing the instance, is ignored on config updates
|
||||||
rustpotterBuilder.setBitsPerSample(bitDepth);
|
rustpotterConfig.setSampleFormat(getIntSampleFormat(bitDepth));
|
||||||
rustpotterBuilder.setSampleRate(frequency);
|
rustpotterConfig.setSampleRate(frequency);
|
||||||
rustpotterBuilder.setChannels(channels);
|
rustpotterConfig.setChannels(channels);
|
||||||
rustpotterBuilder.setSampleFormat(SampleFormat.INT);
|
rustpotterConfig.setEndianness(endianness);
|
||||||
rustpotterBuilder.setEndianness(endianness);
|
|
||||||
// detector configs
|
|
||||||
rustpotterBuilder.setThreshold(config.threshold);
|
|
||||||
rustpotterBuilder.setAveragedThreshold(config.averagedThreshold);
|
|
||||||
rustpotterBuilder.setScoreMode(getScoreMode(config.scoreMode));
|
|
||||||
rustpotterBuilder.setMinScores(config.minScores);
|
|
||||||
rustpotterBuilder.setComparatorRef(config.comparatorRef);
|
|
||||||
rustpotterBuilder.setComparatorBandSize(config.comparatorBandSize);
|
|
||||||
// filter configs
|
|
||||||
rustpotterBuilder.setGainNormalizerEnabled(config.gainNormalizer);
|
|
||||||
rustpotterBuilder.setMinGain(config.minGain);
|
|
||||||
rustpotterBuilder.setMaxGain(config.maxGain);
|
|
||||||
rustpotterBuilder.setGainRef(config.gainRef);
|
|
||||||
rustpotterBuilder.setBandPassFilterEnabled(config.bandPass);
|
|
||||||
rustpotterBuilder.setBandPassLowCutoff(config.lowCutoff);
|
|
||||||
rustpotterBuilder.setBandPassHighCutoff(config.highCutoff);
|
|
||||||
// init the detector
|
// init the detector
|
||||||
var rustpotter = rustpotterBuilder.build();
|
var rustpotter = new Rustpotter(rustpotterConfig);
|
||||||
rustpotterBuilder.delete();
|
rustpotterConfig.delete();
|
||||||
return rustpotter;
|
return rustpotter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processAudioStream(Rustpotter rustpotter, KSListener ksListener, AudioStream audioStream,
|
private RustpotterConfig initRustpotterConfig() {
|
||||||
AtomicBoolean aborted) {
|
var rustpotterConfig = new RustpotterConfig();
|
||||||
|
// detector configs
|
||||||
|
rustpotterConfig.setThreshold(config.threshold);
|
||||||
|
rustpotterConfig.setAveragedThreshold(config.averagedThreshold);
|
||||||
|
rustpotterConfig.setScoreMode(getScoreMode(config.scoreMode));
|
||||||
|
rustpotterConfig.setMinScores(config.minScores);
|
||||||
|
rustpotterConfig.setEager(config.eager);
|
||||||
|
rustpotterConfig.setScoreRef(config.scoreRef);
|
||||||
|
rustpotterConfig.setBandSize(config.bandSize);
|
||||||
|
rustpotterConfig.setVADMode(getVADMode(config.vadMode));
|
||||||
|
rustpotterConfig.setRecordPath(config.record ? RUSTPOTTER_RECORDS_FOLDER.toString() : null);
|
||||||
|
// filter configs
|
||||||
|
rustpotterConfig.setGainNormalizerEnabled(config.gainNormalizer);
|
||||||
|
rustpotterConfig.setMinGain(config.minGain);
|
||||||
|
rustpotterConfig.setMaxGain(config.maxGain);
|
||||||
|
rustpotterConfig.setGainRef(config.gainRef);
|
||||||
|
rustpotterConfig.setBandPassFilterEnabled(config.bandPass);
|
||||||
|
rustpotterConfig.setBandPassLowCutoff(config.lowCutoff);
|
||||||
|
rustpotterConfig.setBandPassHighCutoff(config.highCutoff);
|
||||||
|
|
||||||
|
return rustpotterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAudioStream(RustpotterMutex rustpotter, int bufferSize, long bytesPerMs, KSListener ksListener,
|
||||||
|
AudioStream audioStream, AtomicBoolean aborted) {
|
||||||
int numBytesRead;
|
int numBytesRead;
|
||||||
var bufferSize = (int) rustpotter.getBytesPerFrame();
|
|
||||||
byte[] audioBuffer = new byte[bufferSize];
|
byte[] audioBuffer = new byte[bufferSize];
|
||||||
int remaining = bufferSize;
|
int remaining = bufferSize;
|
||||||
|
boolean hasFailed = false;
|
||||||
while (!aborted.get()) {
|
while (!aborted.get()) {
|
||||||
try {
|
try {
|
||||||
numBytesRead = audioStream.read(audioBuffer, bufferSize - remaining, remaining);
|
numBytesRead = audioStream.read(audioBuffer, bufferSize - remaining, remaining);
|
||||||
|
@ -196,10 +214,20 @@ public class RustpotterKSService implements KSService {
|
||||||
}
|
}
|
||||||
if (numBytesRead != remaining) {
|
if (numBytesRead != remaining) {
|
||||||
remaining = remaining - numBytesRead;
|
remaining = remaining - numBytesRead;
|
||||||
|
try {
|
||||||
|
Thread.sleep(remaining / bytesPerMs);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
logger.warn("Thread interrupted while waiting for audio, aborting execution");
|
||||||
|
aborted.set(true);
|
||||||
|
}
|
||||||
|
if (aborted.get()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
remaining = bufferSize;
|
remaining = bufferSize;
|
||||||
var result = rustpotter.processBytes(audioBuffer);
|
var result = rustpotter.processBytes(audioBuffer);
|
||||||
|
hasFailed = false;
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
var detection = result.get();
|
var detection = result.get();
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
|
@ -219,33 +247,102 @@ public class RustpotterKSService implements KSService {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
String errorMessage = e.getMessage();
|
String errorMessage = e.getMessage();
|
||||||
ksListener.ksEventReceived(new KSErrorEvent(errorMessage != null ? errorMessage : "Unexpected error"));
|
ksListener.ksEventReceived(new KSErrorEvent(errorMessage != null ? errorMessage : "Unexpected error"));
|
||||||
|
if (hasFailed) {
|
||||||
|
logger.warn("Multiple consecutive errors, stopping service");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
hasFailed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (this.runningInstances) {
|
||||||
|
this.runningInstances.remove(rustpotter);
|
||||||
}
|
}
|
||||||
rustpotter.delete();
|
rustpotter.delete();
|
||||||
logger.debug("rustpotter stopped");
|
logger.debug("Rustpotter stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void asyncUpdateActiveInstances() {
|
||||||
|
int nInstances;
|
||||||
|
synchronized (this.runningInstances) {
|
||||||
|
nInstances = this.runningInstances.size();
|
||||||
|
}
|
||||||
|
if (nInstances == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var rustpotterConfig = initRustpotterConfig();
|
||||||
|
executor.submit(() -> {
|
||||||
|
logger.debug("Updating running instances");
|
||||||
|
synchronized (this.runningInstances) {
|
||||||
|
for (RustpotterMutex rustpotter : this.runningInstances) {
|
||||||
|
rustpotter.updateConfig(rustpotterConfig);
|
||||||
|
}
|
||||||
|
logger.debug("{} running instances updated", this.runningInstances.size());
|
||||||
|
}
|
||||||
|
rustpotterConfig.delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SampleFormat getIntSampleFormat(int bitDepth) throws IOException {
|
||||||
|
return switch (bitDepth) {
|
||||||
|
case 8 -> SampleFormat.I8;
|
||||||
|
case 16 -> SampleFormat.I16;
|
||||||
|
case 32 -> SampleFormat.I32;
|
||||||
|
default -> throw new IOException("Unsupported audio bit depth: " + bitDepth);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreMode getScoreMode(String mode) {
|
private ScoreMode getScoreMode(String mode) {
|
||||||
switch (mode) {
|
return switch (mode) {
|
||||||
case "average":
|
case "average" -> ScoreMode.AVG;
|
||||||
return ScoreMode.AVG;
|
case "median" -> ScoreMode.MEDIAN;
|
||||||
case "median":
|
case "p25" -> ScoreMode.P25;
|
||||||
return ScoreMode.MEDIAN;
|
case "p50" -> ScoreMode.P50;
|
||||||
case "p25":
|
case "p75" -> ScoreMode.P75;
|
||||||
return ScoreMode.P25;
|
case "p80" -> ScoreMode.P80;
|
||||||
case "p50":
|
case "p90" -> ScoreMode.P90;
|
||||||
return ScoreMode.P50;
|
case "p95" -> ScoreMode.P95;
|
||||||
case "p75":
|
default -> ScoreMode.MAX;
|
||||||
return ScoreMode.P75;
|
};
|
||||||
case "p80":
|
}
|
||||||
return ScoreMode.P80;
|
|
||||||
case "p90":
|
private @Nullable VADMode getVADMode(String mode) {
|
||||||
return ScoreMode.P90;
|
return switch (mode) {
|
||||||
case "p95":
|
case "easy" -> VADMode.EASY;
|
||||||
return ScoreMode.P95;
|
case "medium" -> VADMode.MEDIUM;
|
||||||
case "max":
|
case "hard" -> VADMode.HARD;
|
||||||
default:
|
default -> null;
|
||||||
return ScoreMode.MAX;
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryCreateDir(Path rustpotterFolder) {
|
||||||
|
if (!Files.exists(rustpotterFolder) || !Files.isDirectory(rustpotterFolder)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(rustpotterFolder);
|
||||||
|
logger.info("Folder {} created", rustpotterFolder);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Unable to create folder {}", rustpotterFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RustpotterMutex(Rustpotter rustpotter) {
|
||||||
|
|
||||||
|
public Optional<RustpotterDetection> processBytes(byte[] bytes) {
|
||||||
|
synchronized (this.rustpotter) {
|
||||||
|
return this.rustpotter.processBytes(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateConfig(RustpotterConfig config) {
|
||||||
|
synchronized (this.rustpotter) {
|
||||||
|
this.rustpotter.updateConfig(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
synchronized (this.rustpotter) {
|
||||||
|
this.rustpotter.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
|
|
||||||
<type>voice</type>
|
<type>voice</type>
|
||||||
<name>Rustpotter Keyword Spotter</name>
|
<name>Rustpotter Keyword Spotter</name>
|
||||||
<description>This voice service allows you to use the open source library Rustpotter as your keyword spotter in
|
<description>This voice service allows using the open source project Rustpotter as your keyword spotter in openHAB.</description>
|
||||||
openHAB.</description>
|
|
||||||
<connection>none</connection>
|
<connection>none</connection>
|
||||||
|
|
||||||
<service-id>org.openhab.voice.rustpotterks</service-id>
|
<service-id>org.openhab.voice.rustpotterks</service-id>
|
||||||
|
|
|
@ -13,24 +13,22 @@
|
||||||
<label>Audio Filters</label>
|
<label>Audio Filters</label>
|
||||||
<description>Optional audio filter options.</description>
|
<description>Optional audio filter options.</description>
|
||||||
</parameter-group>
|
</parameter-group>
|
||||||
<parameter name="threshold" type="decimal" min="0" max="1" groupName="wakewordDetector">
|
<parameter name="threshold" type="decimal" min="0" max="1" step="0.001" groupName="wakewordDetector">
|
||||||
<label>Threshold</label>
|
<label>Threshold</label>
|
||||||
<description>Configures the detector threshold, is the min score (in range 0. to 1.) that some of the wakeword
|
<description>Configures the detector threshold, is the min score (in range 0. to 1.) to trigger the detection.</description>
|
||||||
templates should obtain to trigger a detection. Model defined value takes prevalence if present.</description>
|
|
||||||
<default>0.5</default>
|
<default>0.5</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="averagedThreshold" type="decimal" min="0" max="1" groupName="wakewordDetector">
|
<parameter name="averagedThreshold" type="decimal" min="0" max="1" step="0.001" groupName="wakewordDetector">
|
||||||
<label>Averaged Threshold</label>
|
<label>Averaged Threshold</label>
|
||||||
<description>Configures the detector averaged threshold, is the min score (in range 0. to 1.) that the audio should
|
<description>Configures the detector averaged threshold. If set to 0 this functionality is disabled.</description>
|
||||||
obtain against a combination of the wake word templates, the detection will be aborted if this is not the case. This
|
<default>0</default>
|
||||||
way it can prevent to run the comparison of the current frame against each of the wake word templates which saves
|
<advanced>true</advanced>
|
||||||
cpu. If set to 0 this functionality is disabled.</description>
|
|
||||||
<default>0.2</default>
|
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="scoreMode" type="text" groupName="wakewordDetector">
|
<parameter name="scoreMode" type="text" groupName="wakewordDetector">
|
||||||
<label>Score Mode</label>
|
<label>Score Mode</label>
|
||||||
<description>Indicates how to calculate the final score.</description>
|
<description>Indicates how to calculate the final score. Not affect to wakeword models.</description>
|
||||||
<default>max</default>
|
<default>max</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
<options>
|
<options>
|
||||||
<option value="average">Average</option>
|
<option value="average">Average</option>
|
||||||
<option value="max">Max</option>
|
<option value="max">Max</option>
|
||||||
|
@ -43,23 +41,47 @@
|
||||||
<option value="p95">P95</option>
|
<option value="p95">P95</option>
|
||||||
</options>
|
</options>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="vadMode" type="text" groupName="wakewordDetector">
|
||||||
|
<label>VAD Mode</label>
|
||||||
|
<description>Enables a basic vad detector to discard some execution.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
<options>
|
||||||
|
<option value="off">Off</option>
|
||||||
|
<option value="easy">Easy</option>
|
||||||
|
<option value="medium">Medium</option>
|
||||||
|
<option value="hard">Hard</option>
|
||||||
|
</options>
|
||||||
|
<default>off</default>
|
||||||
|
</parameter>
|
||||||
<parameter name="minScores" type="integer" groupName="wakewordDetector">
|
<parameter name="minScores" type="integer" groupName="wakewordDetector">
|
||||||
<label>Min Scores</label>
|
<label>Min Scores</label>
|
||||||
<description>Minimum number of positive scores to consider a partial detection as a detection.</description>
|
<description>Minimum number of positive scores to consider a partial detection as a detection.</description>
|
||||||
<default>5</default>
|
<default>5</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="comparatorRef" type="decimal" min="0" max="1" groupName="wakewordDetector">
|
<parameter name="eager" type="boolean" groupName="wakewordDetector">
|
||||||
<label>Comparator Ref</label>
|
<label>Eager</label>
|
||||||
<description>Configures the reference for the comparator used to match the samples.</description>
|
<description>Emit detection on min partial scores.</description>
|
||||||
|
<default>false</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="scoreRef" type="decimal" min="0" max="1" groupName="wakewordDetector">
|
||||||
|
<label>Score Ref</label>
|
||||||
|
<description>Value used to calculate the score as a percent in range 0 - 1.</description>
|
||||||
<default>0.22</default>
|
<default>0.22</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="comparatorBandSize" type="integer" groupName="wakewordDetector">
|
<parameter name="bandSize" type="integer" groupName="wakewordDetector">
|
||||||
<label>Comparator Band Size</label>
|
<label>Band Size</label>
|
||||||
<description>Configures the band-size for the comparator used to match the samples.</description>
|
<description>Configures the band-size for the comparator used to match the wakeword refs. Not affect to wakeword
|
||||||
|
models.</description>
|
||||||
<default>5</default>
|
<default>5</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="record" type="boolean" groupName="wakewordDetector">
|
||||||
|
<label>Record on Partial Detections</label>
|
||||||
|
<description>Create wav record on the first partial detections and any other one that surpasses its score.</description>
|
||||||
|
<default>false</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
<parameter name="gainNormalizer" type="boolean" groupName="filters">
|
<parameter name="gainNormalizer" type="boolean" groupName="filters">
|
||||||
<label>Gain Normalizer</label>
|
<label>Gain Normalizer</label>
|
||||||
<description> Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS
|
<description> Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS
|
||||||
|
@ -76,7 +98,7 @@
|
||||||
<description>Max gain applied by the gain normalizer filter.</description>
|
<description>Max gain applied by the gain normalizer filter.</description>
|
||||||
<default>1</default>
|
<default>1</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="gainRef" type="decimal" min="0" max="1" step="0.001" groupName="filters">
|
<parameter name="gainRef" type="decimal" min="0" max="1" step="0.0001" groupName="filters">
|
||||||
<label>Gain Ref</label>
|
<label>Gain Ref</label>
|
||||||
<description>Set the RMS reference used by the gain-normalizer to calculate the gain applied. If unset an estimation
|
<description>Set the RMS reference used by the gain-normalizer to calculate the gain applied. If unset an estimation
|
||||||
of the wakeword level is used.</description>
|
of the wakeword level is used.</description>
|
||||||
|
@ -85,16 +107,19 @@
|
||||||
<label>Band Pass</label>
|
<label>Band Pass</label>
|
||||||
<description>Enables an audio filter that attenuates frequencies outside the low cutoff and high cutoff range.</description>
|
<description>Enables an audio filter that attenuates frequencies outside the low cutoff and high cutoff range.</description>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="lowCutoff" type="decimal" min="0" groupName="filters">
|
<parameter name="lowCutoff" type="decimal" min="0" groupName="filters">
|
||||||
<label>Low Cutoff</label>
|
<label>Low Cutoff</label>
|
||||||
<description>Low cutoff for the band-pass filter.</description>
|
<description>Low cutoff for the band-pass filter.</description>
|
||||||
<default>80</default>
|
<default>80</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="highCutoff" type="decimal" min="0" groupName="filters">
|
<parameter name="highCutoff" type="decimal" min="0" groupName="filters">
|
||||||
<label>High Cutoff</label>
|
<label>High Cutoff</label>
|
||||||
<description>High cutoff for the band-pass filter.</description>
|
<description>High cutoff for the band-pass filter.</description>
|
||||||
<default>400</default>
|
<default>400</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</config-description:config-descriptions>
|
</config-description:config-descriptions>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
voice.config.rustpotterks.averagedThreshold.label = Averaged Threshold
|
voice.config.rustpotterks.averagedThreshold.label = Averaged Threshold
|
||||||
voice.config.rustpotterks.averagedThreshold.description = Configures the detector averaged threshold, is the min score (in range 0. to 1.) that the audio should obtain against a combination of the wake word templates, the detection will be aborted if this is not the case. This way it can prevent to run the comparison of the current frame against each of the wake word templates which saves cpu. If set to 0 this functionality is disabled.
|
voice.config.rustpotterks.averagedThreshold.description = Configures the detector averaged threshold. If set to 0 this functionality is disabled.
|
||||||
voice.config.rustpotterks.bandPass.label = Band Pass
|
voice.config.rustpotterks.bandPass.label = Band Pass
|
||||||
voice.config.rustpotterks.bandPass.description = Enables an audio filter that attenuates frequencies outside the low cutoff and high cutoff range.
|
voice.config.rustpotterks.bandPass.description = Enables an audio filter that attenuates frequencies outside the low cutoff and high cutoff range.
|
||||||
voice.config.rustpotterks.comparatorBandSize.label = Comparator Band Size
|
voice.config.rustpotterks.bandSize.label = Band Size
|
||||||
voice.config.rustpotterks.comparatorBandSize.description = Configures the band-size for the comparator used to match the samples.
|
voice.config.rustpotterks.bandSize.description = Configures the band-size for the comparator used to match the wakeword refs. Not affect to wakeword models.
|
||||||
voice.config.rustpotterks.comparatorRef.label = Comparator Ref
|
voice.config.rustpotterks.eager.label = Eager
|
||||||
voice.config.rustpotterks.comparatorRef.description = Configures the reference for the comparator used to match the samples.
|
voice.config.rustpotterks.eager.description = Emit detection on min partial scores.
|
||||||
voice.config.rustpotterks.gainNormalizer.label = Gain Normalizer
|
voice.config.rustpotterks.gainNormalizer.label = Gain Normalizer
|
||||||
voice.config.rustpotterks.gainNormalizer.description = Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS of the samples is used as volume measure).
|
voice.config.rustpotterks.gainNormalizer.description = Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS of the samples is used as volume measure).
|
||||||
voice.config.rustpotterks.gainRef.label = Gain Ref
|
voice.config.rustpotterks.gainRef.label = Gain Ref
|
||||||
|
@ -24,8 +24,10 @@ voice.config.rustpotterks.minGain.label = Min Gain
|
||||||
voice.config.rustpotterks.minGain.description = Min gain applied by the gain normalizer filter.
|
voice.config.rustpotterks.minGain.description = Min gain applied by the gain normalizer filter.
|
||||||
voice.config.rustpotterks.minScores.label = Min Scores
|
voice.config.rustpotterks.minScores.label = Min Scores
|
||||||
voice.config.rustpotterks.minScores.description = Minimum number of positive scores to consider a partial detection as a detection.
|
voice.config.rustpotterks.minScores.description = Minimum number of positive scores to consider a partial detection as a detection.
|
||||||
|
voice.config.rustpotterks.record.label = Record on Partial Detections
|
||||||
|
voice.config.rustpotterks.record.description = Create wav record on the first partial detections and any other one that surpasses its score.
|
||||||
voice.config.rustpotterks.scoreMode.label = Score Mode
|
voice.config.rustpotterks.scoreMode.label = Score Mode
|
||||||
voice.config.rustpotterks.scoreMode.description = Indicates how to calculate the final score.
|
voice.config.rustpotterks.scoreMode.description = Indicates how to calculate the final score. Not affect to wakeword models.
|
||||||
voice.config.rustpotterks.scoreMode.option.average = Average
|
voice.config.rustpotterks.scoreMode.option.average = Average
|
||||||
voice.config.rustpotterks.scoreMode.option.max = Max
|
voice.config.rustpotterks.scoreMode.option.max = Max
|
||||||
voice.config.rustpotterks.scoreMode.option.median = Median
|
voice.config.rustpotterks.scoreMode.option.median = Median
|
||||||
|
@ -35,9 +37,18 @@ voice.config.rustpotterks.scoreMode.option.p75 = P75
|
||||||
voice.config.rustpotterks.scoreMode.option.p80 = P80
|
voice.config.rustpotterks.scoreMode.option.p80 = P80
|
||||||
voice.config.rustpotterks.scoreMode.option.p90 = P90
|
voice.config.rustpotterks.scoreMode.option.p90 = P90
|
||||||
voice.config.rustpotterks.scoreMode.option.p95 = P95
|
voice.config.rustpotterks.scoreMode.option.p95 = P95
|
||||||
|
voice.config.rustpotterks.scoreRef.label = Score Ref
|
||||||
|
voice.config.rustpotterks.scoreRef.description = Value used to calculate the score as a percent in range 0 - 1.
|
||||||
voice.config.rustpotterks.threshold.label = Threshold
|
voice.config.rustpotterks.threshold.label = Threshold
|
||||||
voice.config.rustpotterks.threshold.description = Configures the detector threshold, is the min score (in range 0. to 1.) that some of the wakeword templates should obtain to trigger a detection. Model defined value takes prevalence if present.
|
voice.config.rustpotterks.threshold.description = Configures the detector threshold, is the min score (in range 0. to 1.) that some of the wakeword templates should obtain to trigger a detection.
|
||||||
|
voice.config.rustpotterks.vadMode.label = VAD Mode
|
||||||
|
voice.config.rustpotterks.vadMode.description = Enables a basic vad detector to discard some execution.
|
||||||
|
voice.config.rustpotterks.vadMode.option.off = Off
|
||||||
|
voice.config.rustpotterks.vadMode.option.easy = Easy
|
||||||
|
voice.config.rustpotterks.vadMode.option.medium = Medium
|
||||||
|
voice.config.rustpotterks.vadMode.option.hard = Hard
|
||||||
|
|
||||||
# service
|
# add-on
|
||||||
|
|
||||||
service.voice.rustpotterks.label = Rustpotter Keyword Spotter
|
addon.rustpotterks.name = Rustpotter Keyword Spotter
|
||||||
|
addon.rustpotterks.description = This voice service allows using the open source project Rustpotter as your keyword spotter in openHAB.
|
||||||
|
|
|
@ -1,41 +1,54 @@
|
||||||
voice.config.rustpotterks.averagedThreshold.label = Soglia Media
|
voice.config.rustpotterks.averagedThreshold.label = Averaged Threshold
|
||||||
voice.config.rustpotterks.averagedThreshold.description = Configura la soglia media del rilevatore, è il punteggio minimo (in intervallo da 0. a 1.) che l'audio dovrebbe avere rispetto a una combinazione dei modelli di parole di wake up, il rilevamento verrà interrotto se non avviene. In questo modo può impedire di eseguire il confronto del frame corrente con ciascuno dei modelli di parole di wake up che permettono di salvare cpu. Se impostato a 0 questa funzionalità è disabilitata.
|
voice.config.rustpotterks.averagedThreshold.description = Configures the detector averaged threshold. If set to 0 this functionality is disabled.
|
||||||
voice.config.rustpotterks.comparatorBandSize.label = Dimensione Banda Di Comparazione
|
voice.config.rustpotterks.bandPass.label = Band Pass
|
||||||
voice.config.rustpotterks.comparatorBandSize.description = Configura la dimensione della banda per il comparatore usato per abbinare i campioni.
|
voice.config.rustpotterks.bandPass.description = Enables an audio filter that attenuates frequencies outside the low cutoff and high cutoff range.
|
||||||
voice.config.rustpotterks.comparatorRef.label = Rif. Comparatore
|
voice.config.rustpotterks.bandSize.label = Band Size
|
||||||
voice.config.rustpotterks.comparatorRef.description = Configura il riferimento per il comparatore utilizzato per abbinare i campioni.
|
voice.config.rustpotterks.bandSize.description = Configures the band-size for the comparator used to match the wakeword refs. Not affect to wakeword models.
|
||||||
voice.config.rustpotterks.eagerMode.label = Modalità Eager
|
voice.config.rustpotterks.eager.label = Eager
|
||||||
voice.config.rustpotterks.eagerMode.description = Abilita la modalità eager. Termina il rilevamento non appena un risultato è sopra il punteggio, invece di aspettare di vedere se il quadro successivo ha un punteggio più alto.
|
voice.config.rustpotterks.eager.description = Emit detection on min partial scores.
|
||||||
voice.config.rustpotterks.group.noiseDetector.label = Rilevatore Rumore
|
voice.config.rustpotterks.gainNormalizer.label = Gain Normalizer
|
||||||
voice.config.rustpotterks.group.noiseDetector.description = Opzioni aggiuntive di rilevamento del rumore.
|
voice.config.rustpotterks.gainNormalizer.description = Enables an audio filter that intent to approximate the volume of the stream to a reference level (RMS of the samples is used as volume measure).
|
||||||
voice.config.rustpotterks.group.vadDetector.label = Rilevatore VAD
|
voice.config.rustpotterks.gainRef.label = Gain Ref
|
||||||
voice.config.rustpotterks.group.vadDetector.description = Opzioni aggiuntive del rilevatore di attività vocale.
|
voice.config.rustpotterks.gainRef.description = Set the RMS reference used by the gain-normalizer to calculate the gain applied. If unset an estimation of the wakeword level is used.
|
||||||
voice.config.rustpotterks.group.wakewordDetector.label = Rivelatore Wakeword
|
voice.config.rustpotterks.group.filters.label = Audio Filters
|
||||||
voice.config.rustpotterks.group.wakewordDetector.description = Opzioni di rilevamento Wakeword.
|
voice.config.rustpotterks.group.filters.description = Optional audio filter options.
|
||||||
voice.config.rustpotterks.noiseDetectionMode.label = Modalità Rilevamento Rumore
|
voice.config.rustpotterks.group.wakewordDetector.label = Wakeword Detector
|
||||||
voice.config.rustpotterks.noiseDetectionMode.description = Utilizzare un rilevatore di rumore per ridurre il calcolo in assenza di suono. Configura la difficoltà di considerare un fotogramma come rumore (il livello di rumore richiesto).
|
voice.config.rustpotterks.group.wakewordDetector.description = Wakeword detection options.
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.disabled = Disabilitato
|
voice.config.rustpotterks.highCutoff.label = High Cutoff
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.easiest = Più Semplice
|
voice.config.rustpotterks.highCutoff.description = High cutoff for the band-pass filter.
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.easy = Facile
|
voice.config.rustpotterks.lowCutoff.label = Low Cutoff
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.normal = Normale
|
voice.config.rustpotterks.lowCutoff.description = Low cutoff for the band-pass filter.
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.hard = Difficile
|
voice.config.rustpotterks.maxGain.label = Max Gain
|
||||||
voice.config.rustpotterks.noiseDetectionMode.option.hardest = Molto Difficile
|
voice.config.rustpotterks.maxGain.description = Max gain applied by the gain normalizer filter.
|
||||||
voice.config.rustpotterks.noiseSensitivity.label = Sensibilità Al Rumore
|
voice.config.rustpotterks.minGain.label = Min Gain
|
||||||
voice.config.rustpotterks.noiseSensitivity.description = Il rapporto rumore/silenzio nell'ultimo secondo per considerare la voce come rilevata.
|
voice.config.rustpotterks.minGain.description = Min gain applied by the gain normalizer filter.
|
||||||
voice.config.rustpotterks.threshold.label = Soglia
|
voice.config.rustpotterks.minScores.label = Min Scores
|
||||||
voice.config.rustpotterks.threshold.description = Configura la soglia del rilevatore, è il punteggio minimo (in intervallo da 0. a 1.) che alcuni modelli di wakeword dovrebbero ottenere per attivare un rilevamento. Il valore definito modello prende la prevalenza se presente.
|
voice.config.rustpotterks.minScores.description = Minimum number of positive scores to consider a partial detection as a detection.
|
||||||
voice.config.rustpotterks.vadDelay.label = Ritardo VAD
|
voice.config.rustpotterks.record.label = Record on Partial Detections
|
||||||
voice.config.rustpotterks.vadDelay.description = Secondi per disabilitare il rivelatore vad dopo che la voce è stata rilevata.
|
voice.config.rustpotterks.record.description = Create wav record on the first partial detections and any other one that surpasses its score.
|
||||||
voice.config.rustpotterks.vadMode.label = Modalità VAD
|
voice.config.rustpotterks.scoreMode.label = Score Mode
|
||||||
voice.config.rustpotterks.vadMode.description = Utilizzare un rivelatore vad per ridurre il calcolo in assenza di suono vocale.
|
voice.config.rustpotterks.scoreMode.description = Indicates how to calculate the final score. Not affect to wakeword models.
|
||||||
voice.config.rustpotterks.vadMode.option.disabled = Disabilitato
|
voice.config.rustpotterks.scoreMode.option.average = Average
|
||||||
voice.config.rustpotterks.vadMode.option.low-bitrate = Basso Bitrate
|
voice.config.rustpotterks.scoreMode.option.max = Max
|
||||||
voice.config.rustpotterks.vadMode.option.quality = Qualità
|
voice.config.rustpotterks.scoreMode.option.median = Median
|
||||||
voice.config.rustpotterks.vadMode.option.aggressive = Aggressivo
|
voice.config.rustpotterks.scoreMode.option.p25 = P25
|
||||||
voice.config.rustpotterks.vadMode.option.very-aggressive = Molto aggressivo
|
voice.config.rustpotterks.scoreMode.option.p50 = P50
|
||||||
voice.config.rustpotterks.vadSensitivity.label = Sensibilità VAD
|
voice.config.rustpotterks.scoreMode.option.p75 = P75
|
||||||
voice.config.rustpotterks.vadSensitivity.description = Il rapporto voce/silenzio nell'ultimo secondo per considerare la voce come rilevata.
|
voice.config.rustpotterks.scoreMode.option.p80 = P80
|
||||||
|
voice.config.rustpotterks.scoreMode.option.p90 = P90
|
||||||
|
voice.config.rustpotterks.scoreMode.option.p95 = P95
|
||||||
|
voice.config.rustpotterks.scoreRef.label = Score Ref
|
||||||
|
voice.config.rustpotterks.scoreRef.description = Value used to calculate the score as a percent in range 0 - 1.
|
||||||
|
voice.config.rustpotterks.threshold.label = Threshold
|
||||||
|
voice.config.rustpotterks.threshold.description = Configures the detector threshold, is the min score (in range 0. to 1.) that some of the wakeword templates should obtain to trigger a detection.
|
||||||
|
voice.config.rustpotterks.vadMode.label = VAD Mode
|
||||||
|
voice.config.rustpotterks.vadMode.description = Enables a basic vad detector to discard some execution.
|
||||||
|
voice.config.rustpotterks.vadMode.option.off = Off
|
||||||
|
voice.config.rustpotterks.vadMode.option.easy = Easy
|
||||||
|
voice.config.rustpotterks.vadMode.option.medium = Medium
|
||||||
|
voice.config.rustpotterks.vadMode.option.hard = Hard
|
||||||
|
|
||||||
# service
|
# add-on
|
||||||
|
|
||||||
service.voice.rustpotterks.label = Parola Chiave Spotter per Rustpotter
|
addon.rustpotterks.name = Rustpotter Keyword Spotter
|
||||||
|
addon.rustpotterks.description = This voice service allows using the open source project Rustpotter as your keyword spotter in openHAB.
|
||||||
|
|
Loading…
Reference in New Issue