| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
| |
Oops, I meant to check this in a while back.
Since transcribe_v2.py now has feature parity with transcribe.py, delete
the old code.
|
| |
|
|
|
|
|
|
| |
FuzzyRepeatCommitter was approximating this behavior in the
best-performing configuration, so switch to it in earnest.
This committer simply commits audio once we detect a long enough gap in
speech. That's it!
|
| | |
|
| |
|
|
| |
If not set, the prefab will have its audio sources removed.
|
| |
|
|
|
| |
Duplicating config between args and config is a huge pain in the ass to
maintain. Now we just launch using the config generated by the UI. ezpz.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
* Temporarily restore normal process priority. Working on adding a UI
option to set STT prio.
* Give audio indicator phonemes a 1/3 chance to do nothing. Makes result
sound a little better imo.
* Quiet down steamVR thread when steamVR isn't running
* Fix use of `button_id` and `hand_id` in steamvr.py
* Increase amount of silence allowed before transcript from 1 to 5
seconds. You want enough buffer to allow for a few full transcripts,
else you risk spuriously dropping audio.
* Enable background loading in audio metadata (required by vrc sdk)
|
| |
|
|
|
|
|
|
| |
This is now dynamically set inside transcribe.py.
As the buffer grows long, the threshold grows exponentially, keeping the
buffer short. The threshold starts small so that transcription starts
strict (accurate, slow) and get looser (inaccurate, fast) as needed.
|
| |
|
|
|
| |
openxr doesn't have any notion of background process, making it unusable
trash :)
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I this improves the code structure of the controller input thread and
leads to some deduplication, so I'm going to keep it. However, the
intended purpose was to decrease lag when pressing buttons, and in that
regard it failed.
The lag goes all the way down to the input layer, implying that the
input thread is not able to consistently run at its intended 100 Hz
sample rate. I suspect that the Python global interpreter lock (GIL) is
at fault.
Since we can't realistically move all our functionality into one thread
in a non-blocking model, I think multiprocessing is the logical choice
going forward. Each thread in transcribe.py would become its own
process, and pub/sub through some intermediary process sitting in the
middle.
|
| |
|
|
| |
pyopenvr is both deprecated and buggy, so switch to pyopenxr.
|
| |
|
|
| |
Text box now shows an animated ellipsis prior to first speech.
|
| |
|
|
|
| |
Deprecate the visual and auditory speech indicators, saving 4 bits
across the board. Fixed overhead is now 21 bits.
|
| |
|
|
|
|
|
|
| |
Transcription thread now blocks until microphone thread deletes samples
as requested.
(This is hacky design, it should use a work queue or something, but I
don't feel like doing that right now)
|
| |
|
|
|
|
|
|
|
|
| |
It's possible that the user has toggled off transcription while the
algorithm is still working. In this case we should *not* begin
exponential backoff since there's still work to do.
Also:
* Shorten the hot-path sleep from 50ms to 5ms.
* Remove unused variable in SleepInterruptible
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
When we commit a transcription, we drop the corresponding audio data.
Audio data is represented as a list of chunks. Each chunk contains a few
hundred samples of audio data, representing O(10ms) of audio.
If we want to drop a few seconds of data, this means simply deleting
many chunks of audio. There's usually a chunk where we want to drop some
portion of audio data.
Instead of slicing away that part of the chunk, which would change its
length, this change zeroes it out. This preserves the assumption that
each chunk has the same temporal length.
|
| |
|
|
|
|
| |
We used to drop entire frames only, leading to situations where more
audio is dropped than desired. Now we drop frames down to the precision
of the individual audio sample requested.
|
| |
|
|
|
|
|
|
|
| |
Mostly updating roadmap stuff. Non-VRC use cases are "complete" since I
was mostly targeting streaming. The ability to type into arbitrary text
fields is still somewhat nascent & could be improved.
Also update some other random stuff to be more up to date. KillFrenzy
Avatar Text is now MIT, pog!
|
| |
|
|
|
|
|
|
| |
Common hallucinations sneak in around -0.9 avg_logprob.
Also:
* Limit temperatures to just 0.0. Multiple values cause latency to
occasionally spike.
|
| |
|
|
|
|
|
| |
Surprisingly, these args do not cause transcribe() to omit those
segments from the result, so we have to manually filter them out.
Hallucinated phrases generally have one or both of these params set
high.
|
| |
|
|
| |
Each sample of audio data is a 16-bit int, not an 8-bit int.
|
| |
|
|
|
| |
Each chunk of audio samples should be encoded as a binary string, not as
a list.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
New commit logic would reduce buffer to a size smaller than this,
causing it to hallucinate things like:
* "See you next time!"
* "Thanks for watching!"
* "Bye!"
The hope is that by keeping the buffer at least 5.0 seconds long, as
described in the paper, this will cut down on these events.
|
| |
|
|
|
|
|
|
| |
Circle goes red when speaking, grey when done. Ideally it would be in
the top right portion of the browser source, but this is a good start.
Also, hard-cap transcripts to 4096 chars. This prevents the STT from
lagging during long sessions.
|
| |
|
|
| |
... also print out "Ready!" when the STT is done loading.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
| |
onAudioFramesAvailable would bail out if audio_state.audio_paused is
set, preventing frames from being dropped. This would cause
transcriptions to get repeated sometimes.
Now that frame dropping code always runs.
Also adjust the code structure of the keyboard/VR input handlers to be
more similar.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Audio data is stored in chunks of frames, not in individual frames.
When I commit a transcript, I want to get rid of the portion of the
audio data responsible for that particular transcript. I have code that
does this, but it was dropping a slice of the list assuming that each
sample is stored individually.
Extra fun: Because we have to decimate mic frames, we have to convert
between whisper frames and mic frames to drop the correct amount of
audio data.
|
| |
|
|
|
|
|
| |
Add toggle to UI to enable a profanity filter. It replaces vowels in bad
words with asterisks.
Bugfix: filters now apply to OBS
|
| |
|
|
|
|
|
|
| |
Most transcription output is now gone by default. Users can enable a
more verbose output by toggling `Enable debug mode`.
Bugfix: Toggling off transcription would reset audio state, frequently
resulting in the loss of the last few words spoken.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Recap: In the STT there's an algorithm that tries to determine when a
transcript is "stable" enough to commit. If that is too loose, then
accuracy suffers; if too strict, then the audio buffer eventually fills.
To mitigate the problem, I check whether the last N transcripts are
within some edit distance (Levenshtein edit distance) of each other. The
fuzzy matching lets us forgive small instabilities, like differences in
uppercase/lowercase or punctuation, while rejecting large instabilities.
The default value of 8 seems to be in the sweet spot of accuracy &
performance, but it will likely be tuned in the future.
|
| |
|
|
|
|
|
|
| |
... instead of simple equality.
TODO: add UI for threshold.
Bugfix: Frame::onAppStop() joins the OBS app thread.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
This is useful when streaming. Occasionally the STT can get into
a bad state, and manually segmenting clears it up. However doing so
would clear your accumulated transcript, which isn't always desired. Add
ability to preserve the transcript.
A small wrinkle: the new commit logic requires N consecutive identical
windows before committing. To make this feature play nicely with it, I
had to forcibly commit any preview text that hasn't yet been committed.
Failing to do this would usually cause short utterances / the most
recently said stuff to get wiped out.
|
| |
|
|
| |
Add ability to toggle on/off browser src & configure port.
|
| |
|
|
|
| |
Hitting the desktop keybinding to stop transcription would sometimes
cause the last transcript to repeeat itself.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Transcription output now streams to localhost:8097.
In OBS:
* Create a browser source.
* url: localhost:8097
* width: 2200
* height: 400
TODO:
* Put behind toggle.
* Create input field for port.
Misc cleanup:
* transcribe.py: Drop frames from audio capture thread instead of the
transcription thread. Doing it the other way would result in
occasional data loss.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
At the core of the STT, there's a loop which uses Whisper to convert
audio into a transcript. As you say something, whisper sees growing
fragments of your sentence:
t0: "Hell"
t1: "Hello"
t2: "Hello, world!"
So we need some algorithm which takes these fragments and
accumulates them into an ever-growing transcript.
Previously I did this with fuzzy string matching. I'd find the region
where the two transcripts overlap and edit the two together to produce a
longer transcript. The big problem is that if there's no overlap, it's
not clear whether whisper radically changed its mind as to what was
said, or whether the user paused for a long time before saying
something new. So I'd have to reset the growing transcript.
Now I get the timestamps from Whisper and wait for it to give me the
same 3 transcripts for the last utterance. Once the transcript
stabilizes like this, I commit the text. This enables a temporally
stable, ever-growing transcript that's also quite accurate.
To prevent a latency regression, I also introduce the notion of "preview
text", which is a preview of an utterance that has not yet stabilized.
These previews do not contribute to the ever-growing transcript, but do
get fed through the rest of the app, so they show up in-game / in OBS.
Once they eventually stabilize, they get committed to the ever-growing
transcript.
This change is lightly tested!
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
NLLB needs its input to be split up into sentences. I use the
sentence_splitter Python package to do this. It supports ~20 Western
European languages, but notably, no Asian languages.
* Sort spoken language list. English is still at the top.
* Remove 'Translation source' dropdown. Infer this from the spoken
language.
* Add lang_compat.py to map language codes between the various libraries
(whisper, nllb, sentence_splitter).
* Fix bug where old text would appear in textbox when you first bring it
up.
|
| |
|
|
|
|
|
|
|
| |
Use Meta's No Language Left Behind (NLLB) algorithm to provide
translation capabilities into 200 languages. Obviously most are very
untested.
This requires either 4.1 or 7.1 GB of RAM and significiantly increases
transcription latency.
|
| |
|
|
|
|
|
|
|
|
| |
Add 3 filters:
* Remove trailing period
* Convert to uppercase
* Convert to lowercase
All may be composed. Upper/lower just overwrite each other so just use
one.
|
| |
|
|
|
| |
UI now has a checkbox for the uwu filter. Does not materially affect
resource usage or latency when enabled.
|
| |
|
|
|
|
| |
Use UwwwuPP to translate your boring old speech into uwu-ified version.
Still need to add a UI toggle for this.
|
| |
|
|
|
|
|
|
|
|
| |
To use it, do a medium hold + long hold. Keep the long hold depressed
until you're done speaking. The transcription will be typed into the
currently selected input field.
* Add more audio feedback
* Make audio feedback play asynchronously so it doesn't slow down the
controller input state machine as much.
|
| |
|
|
|
|
|
| |
By holding the button while talking for at least 1.5 seconds, you can
update the contents of the textbox without unlocking it from worldspace.
So now you can carefully position your textbox once, then continually
speak into it without having to reposition it every time.
|
| |
|
|
|
|
| |
Users can now configure a keybind to start/stop/dismiss the STT when in
desktop mode. The default keybind is ctrl+x, since by default VRC
doesn't use 'x' for anything.
|
| | |
|
| |
|
|
|
|
| |
Useful on devices with multiple GPUs, such as gaming laptops.
* Update GUI/README.md.
|
| |
|
|
|
|
| |
Affinity mask no longer affects performance. String matching is still
needed for temporal stability in fast-paced long-form transcription
tasks.
|
| |
|
|
| |
I'm able to use the new code to show text in game. Not yet play-tested.
|
| |
|
|
|
|
| |
This is a much faster, lower-VRAM reimplementation of Whisper in Python.
Early testing is extremely promising: fast transcription speed,
extremely low resource usage (CPU/RAM/VRAM), high accuracy.
|
| |
|
|
|
|
|
|
|
| |
A user saw an error like `ModuleNotFoundError: No module named _socket`.
StackOverflow blames this on PYTHONPATH, so let's try setting it.
* Fix latent bug in Scripts/transcribe.py. PyAudio.open() positional
parameters must be specified in correct order, even when telling it
which parameter is which. *shrug*
|