Skip to content

Fixing timing of index events without causing crackling #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions addon/synthDrivers/_ibmeci.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ class ECILanguageDialect:
params = {}
vparams = {}


class EciThread(threading.Thread):
def run(self):
global vparams, params, speaking, endMarkersCount
Expand Down Expand Up @@ -315,7 +314,7 @@ def setLast(lp):
lastindex = lp
onIndexReached(lp)

def bgPlay(stri):
def bgPlay(stri, onDone=None):
global player, currentSampleRate
if not player or len(stri) == 0: return
# Sometimes player.feed() tries to open the device when it's already open,
Expand All @@ -324,7 +323,7 @@ def bgPlay(stri):
tries = 0
while tries < 10:
try:
player.feed(stri)
player.feed(stri, onDone=onDone)
if tries > 0:
log.warning("Eloq speech retries: %d" % (tries))
return
Expand All @@ -339,31 +338,53 @@ def bgPlay(stri):
log.error("Eloq speech failed to feed one buffer.")

indexes = []
def sendIndexes():
global indexes
for i in indexes: _callbackExec(setLast, i)
indexes = []
def sendIndexes(indexes):
for i in indexes: setLast(i)

def playStream():
global audioStream
_callbackExec(bgPlay, audioStream.getvalue())
def playStream(flushSize=None):
global audioStream, indexes
localIndexes = indexes
indexes = []
onDone = None if len(localIndexes) == 0 else lambda localIndexes=localIndexes: sendIndexes(localIndexes)
if audioStream.tell() == 0:
# onDone callback from player.feed behaves weirdly when buffer is empty, so writing a single 16-bit zero to avoid that
# This can only happen when speech sequence starts with an index command and doesn't cause any audible crackling.
audioStream.write(b'\0\0')
flushBytes = audioStream.getvalue()
if flushSize is not None:
keepBytes = flushBytes[flushSize:]
flushBytes = flushBytes[:flushSize]
_callbackExec(bgPlay, flushBytes, onDone=onDone)
audioStream.truncate(0)
audioStream.seek(0)
sendIndexes()
if flushSize is not None:
audioStream.write(keepBytes)

endStringReached = False

@WINFUNCTYPE(c_int, c_int, c_int, c_int, c_void_p)
def eciCallback (h, ms, lp, dt):
global audioStream, speaking, END_STRING_MARK, endMarkersCount, indexes, endStringReached
if speaking and ms == ECIMessage.eciWaveformBuffer:
if (
(
audioStream.tell() >= samples*2
and lp*2 >= samples*2
)
or len(indexes) > 0
):
playStream()
audioStream.write(string_at(buffer, lp*2))
if audioStream.tell() >= samples*2: playStream()
endStringReached = False
if audioStream.tell() >= samples*4:
# This condition doesn't trigger during normal operation. It only triggers when eci repeatedly sends small buffer chunks less than buffer size.
# Nevertheless we don't want to fail in case this happens due to unforeseen circumstances.
# Flushing leading flushSize bytes while keeping the rest in the buffer to avoid sending too short chunks to player.
flushSize = audioStream.tell() - samples*2
playStream(flushSize=flushSize)
elif ms==ECIMessage.eciIndexReply:
if lp == END_STRING_MARK:
if audioStream.tell() > 0: playStream()
sendIndexes()
playStream()
_callbackExec(endStringEvent)
endStringReached = True
else:
Expand Down Expand Up @@ -536,7 +557,7 @@ def endStringEvent():
endMarkersCount -=1
if endMarkersCount == 0:
speaking = False
idleTimer = threading.Timer(0.3, idlePlayer)
idleTimer = threading.Timer(0.0001, idlePlayer)
idleTimer.start()

def idlePlayer():
Expand Down