Skip to content

Commit cb12a2a

Browse files
authored
Merge d324894 into fa619c2
2 parents fa619c2 + d324894 commit cb12a2a

6 files changed

Lines changed: 288 additions & 7 deletions

File tree

nvdaHelper/espeak/sconscript

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,10 +1034,13 @@ def espeak_compileDict_buildAction(
10341034
return ACTION_SUCCESS
10351035

10361036

1037-
sonicLib = env.StaticLibrary(
1037+
sonicLib = env.SharedLibrary(
10381038
target="sonic",
10391039
srcdir=sonicSrcDir.abspath,
1040-
source="sonic.c",
1040+
source=[
1041+
"sonic.c",
1042+
Dir(".").File("sonic.def"),
1043+
],
10411044
)
10421045

10431046
espeakLib = env.SharedLibrary(
@@ -1082,7 +1085,6 @@ espeakLib = env.SharedLibrary(
10821085
"tr_languages.c",
10831086
"voices.c",
10841087
"wavegen.c",
1085-
sonicLib,
10861088
# espeak OPT_SPEECHPLAYER block
10871089
"sPlayer.c",
10881090
"../speechPlayer/src/frame.cpp",
@@ -1100,7 +1102,8 @@ espeakLib = env.SharedLibrary(
11001102
# com\ttsengine.cpp
11011103
# We do not use the ASYNC compile option in espeak.
11021104
],
1103-
LIBS=["advapi32"],
1105+
LIBS=["advapi32", "sonic"],
1106+
LIBPATH=".",
11041107
)
11051108

11061109

@@ -1151,6 +1154,7 @@ for dictFileName, (langCode, inputFiles) in espeakDictionaryCompileList.items():
11511154
)
11521155

11531156
env.Install(synthDriversDir, espeakLib)
1157+
env.Install(synthDriversDir, sonicLib)
11541158

11551159
# install espeak-ng-data
11561160
targetEspeakDataDir = synthDriversDir.Dir("espeak-ng-data")

nvdaHelper/espeak/sonic.def

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
EXPORTS
2+
sonicCreateStream
3+
sonicDestroyStream
4+
sonicSetUserData
5+
sonicGetUserData
6+
sonicWriteFloatToStream
7+
sonicWriteShortToStream
8+
sonicWriteUnsignedCharToStream
9+
sonicReadFloatFromStream
10+
sonicReadShortFromStream
11+
sonicReadUnsignedCharFromStream
12+
sonicFlushStream
13+
sonicSamplesAvailable
14+
sonicGetSpeed
15+
sonicSetSpeed
16+
sonicGetPitch
17+
sonicSetPitch
18+
sonicGetRate
19+
sonicSetRate
20+
sonicGetVolume
21+
sonicSetVolume
22+
sonicGetChordPitch
23+
sonicSetChordPitch
24+
sonicGetQuality
25+
sonicSetQuality
26+
sonicGetSampleRate
27+
sonicSetSampleRate
28+
sonicGetNumChannels
29+
sonicSetNumChannels
30+
sonicChangeFloatSpeed
31+
sonicChangeShortSpeed

source/speech/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,15 @@
149149
import config
150150
from .speech import initialize as speechInitialize
151151
from .sayAll import initialize as sayAllInitialize
152+
from synthDrivers._sonic import initialize as sonicInitialize
152153

153154

154155
def initialize():
155156
"""Loads and sets the synth driver configured in nvda.ini.
156157
Initializes the state of speech and initializes the sayAllHandler
157158
"""
158159
synthDriverHandler.initialize()
160+
sonicInitialize()
159161
synthDriverHandler.setSynth(config.conf["speech"]["synth"])
160162
speechInitialize()
161163
sayAllInitialize(

source/synthDrivers/_sonic.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
from ctypes import Array, c_float, c_int, c_short, c_ubyte, c_void_p, cdll
2+
import os
3+
import globalVars
4+
5+
sonicLib = None
6+
7+
8+
class SonicStreamP(c_void_p):
9+
pass
10+
11+
12+
def initialize():
13+
"""Initialize the Sonic DLL.
14+
The sonic.dll file should be in the installation directory."""
15+
global sonicLib
16+
sonicLib = cdll.LoadLibrary(os.path.join(globalVars.appDir, "synthDrivers", "sonic.dll"))
17+
sonicLib.sonicCreateStream.restype = SonicStreamP
18+
sonicLib.sonicCreateStream.argtypes = [c_int, c_int]
19+
sonicLib.sonicDestroyStream.restype = None
20+
sonicLib.sonicDestroyStream.argtypes = [SonicStreamP]
21+
sonicLib.sonicWriteFloatToStream.restype = c_int
22+
sonicLib.sonicWriteFloatToStream.argtypes = [SonicStreamP, c_void_p, c_int]
23+
sonicLib.sonicWriteShortToStream.restype = c_int
24+
sonicLib.sonicWriteShortToStream.argtypes = [SonicStreamP, c_void_p, c_int]
25+
sonicLib.sonicWriteUnsignedCharToStream.restype = c_int
26+
sonicLib.sonicWriteUnsignedCharToStream.argtypes = [SonicStreamP, c_void_p, c_int]
27+
sonicLib.sonicReadFloatFromStream.restype = c_int
28+
sonicLib.sonicReadFloatFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
29+
sonicLib.sonicReadShortFromStream.restype = c_int
30+
sonicLib.sonicReadShortFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
31+
sonicLib.sonicReadUnsignedCharFromStream.restype = c_int
32+
sonicLib.sonicReadUnsignedCharFromStream.argtypes = [SonicStreamP, c_void_p, c_int]
33+
sonicLib.sonicFlushStream.restype = c_int
34+
sonicLib.sonicFlushStream.argtypes = [SonicStreamP]
35+
sonicLib.sonicSamplesAvailable.restype = c_int
36+
sonicLib.sonicSamplesAvailable.argtypes = [SonicStreamP]
37+
sonicLib.sonicGetSpeed.restype = c_float
38+
sonicLib.sonicGetSpeed.argtypes = [SonicStreamP]
39+
sonicLib.sonicSetSpeed.restype = None
40+
sonicLib.sonicSetSpeed.argtypes = [SonicStreamP, c_float]
41+
sonicLib.sonicGetPitch.restype = c_float
42+
sonicLib.sonicGetPitch.argtypes = [SonicStreamP]
43+
sonicLib.sonicSetPitch.restype = None
44+
sonicLib.sonicSetPitch.argtypes = [SonicStreamP, c_float]
45+
sonicLib.sonicGetRate.restype = c_float
46+
sonicLib.sonicGetRate.argtypes = [SonicStreamP]
47+
sonicLib.sonicSetRate.restype = None
48+
sonicLib.sonicSetRate.argtypes = [SonicStreamP, c_float]
49+
sonicLib.sonicGetVolume.restype = c_float
50+
sonicLib.sonicGetVolume.argtypes = [SonicStreamP]
51+
sonicLib.sonicSetVolume.restype = None
52+
sonicLib.sonicSetVolume.argtypes = [SonicStreamP, c_float]
53+
sonicLib.sonicGetQuality.restype = c_int
54+
sonicLib.sonicGetQuality.argtypes = [SonicStreamP]
55+
sonicLib.sonicSetQuality.restype = None
56+
sonicLib.sonicSetQuality.argtypes = [SonicStreamP, c_int]
57+
sonicLib.sonicGetSampleRate.restype = c_int
58+
sonicLib.sonicGetSampleRate.argtypes = [SonicStreamP]
59+
sonicLib.sonicSetSampleRate.restype = None
60+
sonicLib.sonicSetSampleRate.argtypes = [SonicStreamP, c_int]
61+
sonicLib.sonicGetNumChannels.restype = c_int
62+
sonicLib.sonicGetNumChannels.argtypes = [SonicStreamP]
63+
sonicLib.sonicSetNumChannels.restype = None
64+
sonicLib.sonicSetNumChannels.argtypes = [SonicStreamP, c_int]
65+
66+
67+
class SonicStream:
68+
"""
69+
Audio stream that wraps the Sonic library to process audio,
70+
which is optimised for speeding up speech by high factors.
71+
Audio data are stored internally as 16-bit integers.
72+
"""
73+
74+
def __init__(self, sampleRate: int, channels: int):
75+
self.stream: SonicStreamP = sonicLib.sonicCreateStream(sampleRate, channels)
76+
if not self.stream:
77+
raise MemoryError()
78+
79+
def __del__(self):
80+
sonicLib.sonicDestroyStream(self.stream)
81+
82+
def writeFloat(self, data: c_void_p, numSamples: int) -> None:
83+
"""Write 32-bit floating point data to be processed into the stream,
84+
where each sample must be between -1 and 1.
85+
:param data: A pointer to 32-bit floating point wave data.
86+
:param numSamples: The number of samples.
87+
Multiply this by channel count to get the total number of values.
88+
:raises MemoryError: If memory allocation failed."""
89+
if not sonicLib.sonicWriteFloatToStream(self.stream, data, numSamples):
90+
raise MemoryError()
91+
92+
def writeShort(self, data: c_void_p, numSamples: int) -> None:
93+
"""Write 16-bit integer data to be processed into the stream.
94+
:param data: A pointer to 16-bit integer wave data.
95+
:param numSamples: The number of samples.
96+
Multiply this by channel count to get the total number of values.
97+
:raises MemoryError: If memory allocation failed."""
98+
if not sonicLib.sonicWriteShortToStream(self.stream, data, numSamples):
99+
raise MemoryError()
100+
101+
def writeUnsignedChar(self, data: c_void_p, numSamples: int) -> None:
102+
"""Write 8-bit unsigned integer data to be processed into the stream.
103+
:param data: A pointer to 8-bit integer wave data.
104+
:param numSamples: The number of samples.
105+
Multiply this by channel count to get the total number of values.
106+
:raises MemoryError: If memory allocation failed."""
107+
if not sonicLib.sonicWriteUnsignedCharToStream(self.stream, data, numSamples):
108+
raise MemoryError()
109+
110+
def readFloat(self) -> Array[c_float]:
111+
"""Read processed data from the stream as 32-bit floating point data."""
112+
samples = self.samplesAvailable
113+
arrayLength = samples * self.channels
114+
buffer = (c_float * arrayLength)()
115+
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
116+
return buffer
117+
118+
def readShort(self) -> Array[c_short]:
119+
"""Read processed data from the stream as 16-bit integer data."""
120+
samples = self.samplesAvailable
121+
arrayLength = samples * self.channels
122+
buffer = (c_short * arrayLength)()
123+
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
124+
return buffer
125+
126+
def readUnsignedChar(self) -> Array[c_ubyte]:
127+
"""Read processed data from the stream as 8-bit unsigned integer data."""
128+
samples = self.samplesAvailable
129+
arrayLength = samples * self.channels
130+
buffer = (c_ubyte * arrayLength)()
131+
sonicLib.sonicReadShortFromStream(self.stream, buffer, samples)
132+
return buffer
133+
134+
def flush(self) -> None:
135+
"""Force the sonic stream to generate output using whatever data it currently has.
136+
No extra delay will be added to the output, but flushing in the middle of words could introduce distortion.
137+
This is usually done when data writing is completed.
138+
:raises MemoryError: If memory allocation failed."""
139+
if not sonicLib.sonicFlushStream(self.stream):
140+
raise MemoryError()
141+
142+
@property
143+
def samplesAvailable(self) -> int:
144+
return sonicLib.sonicSamplesAvailable(self.stream)
145+
146+
@property
147+
def speed(self) -> float:
148+
return sonicLib.sonicGetSpeed(self.stream)
149+
150+
@speed.setter
151+
def speed(self, value: float):
152+
sonicLib.sonicSetSpeed(self.stream, value)
153+
154+
@property
155+
def pitch(self) -> float:
156+
return sonicLib.sonicGetPitch(self.stream)
157+
158+
@pitch.setter
159+
def pitch(self, value: float):
160+
sonicLib.sonicSetPitch(self.stream, value)
161+
162+
@property
163+
def rate(self) -> float:
164+
"""This scales pitch and speed at the same time."""
165+
return sonicLib.sonicGetRate(self.stream)
166+
167+
@rate.setter
168+
def rate(self, value: float):
169+
sonicLib.sonicSetRate(self.stream, value)
170+
171+
@property
172+
def volume(self) -> float:
173+
"""The scaling factor of the stream."""
174+
return sonicLib.sonicGetVolume(self.stream)
175+
176+
@volume.setter
177+
def volume(self, value: float):
178+
sonicLib.sonicSetVolume(self.stream, value)
179+
180+
@property
181+
def quality(self) -> int:
182+
"""Default 0 is virtually as good as 1, but very much faster."""
183+
return sonicLib.sonicGetQuality(self.stream)
184+
185+
@quality.setter
186+
def quality(self, value: int):
187+
sonicLib.sonicSetQuality(self.stream, value)
188+
189+
@property
190+
def sampleRate(self) -> int:
191+
return sonicLib.sonicGetSampleRate(self.stream)
192+
193+
@sampleRate.setter
194+
def sampleRate(self, value: int):
195+
sonicLib.sonicSetSampleRate(self.stream, value)
196+
197+
@property
198+
def channels(self) -> int:
199+
return sonicLib.sonicGetNumChannels(self.stream)
200+
201+
@channels.setter
202+
def channels(self, value: int):
203+
sonicLib.sonicSetNumChannels(self.stream, value)

0 commit comments

Comments
 (0)