Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
12 changes: 8 additions & 4 deletions nvdaHelper/espeak/sconscript
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -1034,10 +1034,13 @@ def espeak_compileDict_buildAction(
return ACTION_SUCCESS


sonicLib = env.StaticLibrary(
sonicLib = env.SharedLibrary(
target="sonic",
srcdir=sonicSrcDir.abspath,
source="sonic.c",
source=[
"sonic.c",
Dir(".").File("sonic.def"),
],
)

espeakLib = env.SharedLibrary(
Expand Down Expand Up @@ -1082,7 +1085,6 @@ espeakLib = env.SharedLibrary(
"tr_languages.c",
"voices.c",
"wavegen.c",
sonicLib,
# espeak OPT_SPEECHPLAYER block
"sPlayer.c",
"../speechPlayer/src/frame.cpp",
Expand All @@ -1100,7 +1102,8 @@ espeakLib = env.SharedLibrary(
# com\ttsengine.cpp
# We do not use the ASYNC compile option in espeak.
],
LIBS=["advapi32"],
LIBS=["advapi32", "sonic"],
LIBPATH=".",
)


Expand Down Expand Up @@ -1151,6 +1154,7 @@ for dictFileName, (langCode, inputFiles) in espeakDictionaryCompileList.items():
)

env.Install(synthDriversDir, espeakLib)
env.Install(synthDriversDir, sonicLib)

# install espeak-ng-data
targetEspeakDataDir = synthDriversDir.Dir("espeak-ng-data")
Expand Down
31 changes: 31 additions & 0 deletions nvdaHelper/espeak/sonic.def
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
EXPORTS
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
sonicCreateStream
sonicDestroyStream
sonicSetUserData
sonicGetUserData
sonicWriteFloatToStream
sonicWriteShortToStream
sonicWriteUnsignedCharToStream
sonicReadFloatFromStream
sonicReadShortFromStream
sonicReadUnsignedCharFromStream
sonicFlushStream
sonicSamplesAvailable
sonicGetSpeed
sonicSetSpeed
sonicGetPitch
sonicSetPitch
sonicGetRate
sonicSetRate
sonicGetVolume
sonicSetVolume
sonicGetChordPitch
sonicSetChordPitch
sonicGetQuality
sonicSetQuality
sonicGetSampleRate
sonicSetSampleRate
sonicGetNumChannels
sonicSetNumChannels
sonicChangeFloatSpeed
sonicChangeShortSpeed
2 changes: 2 additions & 0 deletions source/speech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,15 @@
import config
from .speech import initialize as speechInitialize
from .sayAll import initialize as sayAllInitialize
from synthDrivers._sonic import initialize as sonicInitialize


def initialize():
"""Loads and sets the synth driver configured in nvda.ini.
Initializes the state of speech and initializes the sayAllHandler
"""
synthDriverHandler.initialize()
sonicInitialize()
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
synthDriverHandler.setSynth(config.conf["speech"]["synth"])
speechInitialize()
sayAllInitialize(
Expand Down
203 changes: 203 additions & 0 deletions source/synthDrivers/_sonic.py
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
from ctypes import Array, c_float, c_int, c_short, c_ubyte, c_void_p, cdll
import os
import globalVars

sonicLib = None
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved


class SonicStreamP(c_void_p):
pass


def initialize():
"""Initialize the Sonic DLL.
The sonic.dll file should be in the installation directory."""
global sonicLib
sonicLib = cdll.LoadLibrary(os.path.join(globalVars.appDir, "synthDrivers", "sonic.dll"))
sonicLib.sonicCreateStream.restype = SonicStreamP
sonicLib.sonicCreateStream.argtypes = [c_int, c_int]
sonicLib.sonicDestroyStream.restype = None
sonicLib.sonicDestroyStream.argtypes = [SonicStreamP]
sonicLib.sonicWriteFloatToStream.restype = c_int
sonicLib.sonicWriteFloatToStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicWriteShortToStream.restype = c_int
sonicLib.sonicWriteShortToStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicWriteUnsignedCharToStream.restype = c_int
sonicLib.sonicWriteUnsignedCharToStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicReadFloatFromStream.restype = c_int
sonicLib.sonicReadFloatFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicReadShortFromStream.restype = c_int
sonicLib.sonicReadShortFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicReadUnsignedCharFromStream.restype = c_int
sonicLib.sonicReadUnsignedCharFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
sonicLib.sonicFlushStream.restype = c_int
sonicLib.sonicFlushStream.argtypes = [SonicStreamP]
sonicLib.sonicSamplesAvailable.restype = c_int
sonicLib.sonicSamplesAvailable.argtypes = [SonicStreamP]
sonicLib.sonicGetSpeed.restype = c_float
sonicLib.sonicGetSpeed.argtypes = [SonicStreamP]
sonicLib.sonicSetSpeed.restype = None
sonicLib.sonicSetSpeed.argtypes = [SonicStreamP, c_float]
sonicLib.sonicGetPitch.restype = c_float
sonicLib.sonicGetPitch.argtypes = [SonicStreamP]
sonicLib.sonicSetPitch.restype = None
sonicLib.sonicSetPitch.argtypes = [SonicStreamP, c_float]
sonicLib.sonicGetRate.restype = c_float
sonicLib.sonicGetRate.argtypes = [SonicStreamP]
sonicLib.sonicSetRate.restype = None
sonicLib.sonicSetRate.argtypes = [SonicStreamP, c_float]
sonicLib.sonicGetVolume.restype = c_float
sonicLib.sonicGetVolume.argtypes = [SonicStreamP]
sonicLib.sonicSetVolume.restype = None
sonicLib.sonicSetVolume.argtypes = [SonicStreamP, c_float]
sonicLib.sonicGetQuality.restype = c_int
sonicLib.sonicGetQuality.argtypes = [SonicStreamP]
sonicLib.sonicSetQuality.restype = None
sonicLib.sonicSetQuality.argtypes = [SonicStreamP, c_int]
sonicLib.sonicGetSampleRate.restype = c_int
sonicLib.sonicGetSampleRate.argtypes = [SonicStreamP]
sonicLib.sonicSetSampleRate.restype = None
sonicLib.sonicSetSampleRate.argtypes = [SonicStreamP, c_int]
sonicLib.sonicGetNumChannels.restype = c_int
sonicLib.sonicGetNumChannels.argtypes = [SonicStreamP]
sonicLib.sonicSetNumChannels.restype = None
sonicLib.sonicSetNumChannels.argtypes = [SonicStreamP, c_int]


class SonicStream:
"""
Audio stream that wraps the Sonic library to process audio,
which is optimised for speeding up speech by high factors.
Audio data are stored internally as 16-bit integers.
"""

def __init__(self, sampleRate: int, channels: int):
self.stream: SonicStreamP = sonicLib.sonicCreateStream(sampleRate, channels)
if not self.stream:
raise MemoryError()

def __del__(self):
sonicLib.sonicDestroyStream(self.stream)

def writeFloat(self, data: c_void_p, numSamples: int) -> None:
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
"""Write 32-bit floating point data to be processed into the stream,
where each sample must be between -1 and 1.
:param data: A pointer to 32-bit floating point wave data.
:param numSamples: The number of samples.
Multiply this by channel count to get the total number of values.
:raises MemoryError: If memory allocation failed."""
if not sonicLib.sonicWriteFloatToStream(self.stream, data, numSamples):
raise MemoryError()

def writeShort(self, data: c_void_p, numSamples: int) -> None:
"""Write 16-bit integer data to be processed into the stream.
:param data: A pointer to 16-bit integer wave data.
:param numSamples: The number of samples.
Multiply this by channel count to get the total number of values.
:raises MemoryError: If memory allocation failed."""
if not sonicLib.sonicWriteShortToStream(self.stream, data, numSamples):
raise MemoryError()

def writeUnsignedChar(self, data: c_void_p, numSamples: int) -> None:
"""Write 8-bit unsigned integer data to be processed into the stream.
:param data: A pointer to 8-bit integer wave data.
:param numSamples: The number of samples.
Multiply this by channel count to get the total number of values.
:raises MemoryError: If memory allocation failed."""
if not sonicLib.sonicWriteUnsignedCharToStream(self.stream, data, numSamples):
raise MemoryError()

def readFloat(self) -> Array[c_float]:
"""Read processed data from the stream as 32-bit floating point data."""
samples = self.samplesAvailable
arrayLength = samples * self.channels
buffer = (c_float * arrayLength)()
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
return buffer

def readShort(self) -> Array[c_short]:
"""Read processed data from the stream as 16-bit integer data."""
samples = self.samplesAvailable
arrayLength = samples * self.channels
buffer = (c_short * arrayLength)()
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
return buffer

def readUnsignedChar(self) -> Array[c_ubyte]:
"""Read processed data from the stream as 8-bit unsigned integer data."""
samples = self.samplesAvailable
arrayLength = samples * self.channels
buffer = (c_ubyte * arrayLength)()
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
Comment thread
gexgd0419 marked this conversation as resolved.
Resolved
return buffer

def flush(self) -> None:
"""Force the sonic stream to generate output using whatever data it currently has.
No extra delay will be added to the output, but flushing in the middle of words could introduce distortion.
This is usually done when data writing is completed.
:raises MemoryError: If memory allocation failed."""
if not sonicLib.sonicFlushStream(self.stream):
raise MemoryError()

@property
def samplesAvailable(self) -> int:
return sonicLib.sonicSamplesAvailable(self.stream)

@property
def speed(self) -> float:
return sonicLib.sonicGetSpeed(self.stream)

@speed.setter
def speed(self, value: float):
sonicLib.sonicSetSpeed(self.stream, value)

@property
def pitch(self) -> float:
return sonicLib.sonicGetPitch(self.stream)

@pitch.setter
def pitch(self, value: float):
sonicLib.sonicSetPitch(self.stream, value)

@property
def rate(self) -> float:
"""This scales pitch and speed at the same time."""
return sonicLib.sonicGetRate(self.stream)

@rate.setter
def rate(self, value: float):
sonicLib.sonicSetRate(self.stream, value)

@property
def volume(self) -> float:
"""The scaling factor of the stream."""
return sonicLib.sonicGetVolume(self.stream)

@volume.setter
def volume(self, value: float):
sonicLib.sonicSetVolume(self.stream, value)

@property
def quality(self) -> int:
"""Default 0 is virtually as good as 1, but very much faster."""
return sonicLib.sonicGetQuality(self.stream)

@quality.setter
def quality(self, value: int):
sonicLib.sonicSetQuality(self.stream, value)

@property
def sampleRate(self) -> int:
return sonicLib.sonicGetSampleRate(self.stream)

@sampleRate.setter
def sampleRate(self, value: int):
sonicLib.sonicSetSampleRate(self.stream, value)

@property
def channels(self) -> int:
return sonicLib.sonicGetNumChannels(self.stream)

@channels.setter
def channels(self, value: int):
sonicLib.sonicSetNumChannels(self.stream, value)
Loading