Skip to content
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

Support for Windows OneCore voices included in Windows 10. #7110

Merged
merged 13 commits into from Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 0 additions & 13 deletions nvdaHelper/archBuild_sconscript
Expand Up @@ -75,19 +75,6 @@ env.Append(LINKFLAGS='/OPT:REF') #having symbols usually turns this off but we h

Export('env')

import versionInfo
projectRCSubstDict={
'%version_year%':env['version_year'],
'%version_major%':env['version_major'],
'%version_minor%':env['version_minor'],
'%version_build%':env['version_build'],
'%copyright%':env['copyright'],
'%publisher%':env['publisher'],
'%version%':env['version'],
'%productName%':"%s (%s)"%(versionInfo.name,versionInfo.longName),
}
env['projectResFile']=env.RES(env.Substfile('nvda.rc.subst',SUBST_DICT=projectRCSubstDict))

acrobatAccessRPCStubs=env.SConscript('acrobatAccess_sconscript')
Export('acrobatAccessRPCStubs')
if TARGET_ARCH=='x86':
Expand Down
1 change: 1 addition & 0 deletions nvdaHelper/local/nvdaHelperLocal.def
Expand Up @@ -54,3 +54,4 @@ EXPORTS
dllImportTableHooks_hookSingle
dllImportTableHooks_unhookSingle
audioDucking_shouldDelay
logMessage
133 changes: 133 additions & 0 deletions nvdaHelper/localWin10/oneCoreSpeech.cpp
@@ -0,0 +1,133 @@
/*
Code for C dll bridge to Windows OneCore voices.
This file is a part of the NVDA project.
URL: http://www.nvaccess.org/
Copyright 2016-2017 Tyler Spivey, NV Access Limited.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This license can be found at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/

#include <string>
#include <collection.h>
#include <ppltasks.h>
#include <wrl.h>
#include <robuffer.h>
#include <common/log.h>
#include "oneCoreSpeech.h"

using namespace std;
using namespace Platform;
using namespace Windows::Media::SpeechSynthesis;
using namespace concurrency;
using namespace Windows::Storage::Streams;
using namespace Microsoft::WRL;
using namespace Windows::Media;
using namespace Windows::Foundation::Collections;

byte* getBytes(IBuffer^ buffer) {
// We want direct access to the buffer rather than copying it.
// To do this, we need to get to the IBufferByteAccess interface.
// See http://cm-bloggers.blogspot.com/2012/09/accessing-image-pixel-data-in-ccx.html
ComPtr<IInspectable> insp = reinterpret_cast<IInspectable*>(buffer);
ComPtr<IBufferByteAccess> bufferByteAccess;
if (FAILED(insp.As(&bufferByteAccess))) {
LOG_ERROR(L"Couldn't get IBufferByteAccess from IBuffer");
return nullptr;
}
byte* bytes = nullptr;
bufferByteAccess->Buffer(&bytes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can inst.as fail? I.e. that interface is not supported? and perhaps then the pointer would be NULL?

return bytes;
}

OcSpeech* __stdcall ocSpeech_initialize() {
auto instance = new OcSpeech;
instance->synth = ref new SpeechSynthesizer();
return instance;
}

void __stdcall ocSpeech_terminate(OcSpeech* instance) {
delete instance;
}

void __stdcall ocSpeech_setCallback(OcSpeech* instance, ocSpeech_Callback fn) {
instance->callback = fn;
}

void __stdcall ocSpeech_speak(OcSpeech* instance, char16 *text) {
String^ textStr = ref new String(text);
auto markersStr = make_shared<wstring>();
task<SpeechSynthesisStream ^> speakTask;
try {
speakTask = create_task(instance->synth->SynthesizeSsmlToStreamAsync(textStr));
} catch (Platform::Exception ^e) {
LOG_ERROR(L"Error " << e->HResult << L": " << e->Message->Data());
instance->callback(NULL, 0, NULL);
return;
}
speakTask.then([markersStr] (SpeechSynthesisStream^ speechStream) {
// speechStream->Size is 64 bit, but Buffer can only take 32 bit.
// We shouldn't get values above 32 bit in reality.
const unsigned int size = static_cast<unsigned int>(speechStream->Size);
Buffer^ buffer = ref new Buffer(size);
IVectorView<IMediaMarker^>^ markers = speechStream->Markers;
for (auto&& marker : markers) {
if (markersStr->length() > 0) {
*markersStr += L"|";
}
*markersStr += marker->Text->Data();
*markersStr += L":";
*markersStr += to_wstring(marker->Time.Duration);
}
auto t = create_task(speechStream->ReadAsync(buffer, size, Windows::Storage::Streams::InputStreamOptions::None));
return t;
}).then([instance, markersStr] (IBuffer^ buffer) {
// Data has been read from the speech stream.
// Pass it to the callback.
byte* bytes = getBytes(buffer);
instance->callback(bytes, buffer->Length, markersStr->c_str());
}).then([instance] (task<void> previous) {
// Catch any unhandled exceptions that occurred during these tasks.
try {
previous.get();
} catch (Platform::Exception^ e) {
LOG_ERROR(L"Error " << e->HResult << L": " << e->Message->Data());
instance->callback(NULL, 0, NULL);
}
});
}

// We use BSTR because we need the string to stay around until the caller is done with it
// but the caller then needs to free it.
// We can't just use malloc because the caller might be using a different CRT
// and calling malloc and free from different CRTs isn't safe.
BSTR __stdcall ocSpeech_getVoices(OcSpeech* instance) {
wstring voices;
for (unsigned int i = 0; i < instance->synth->AllVoices->Size; ++i) {
VoiceInformation^ info = instance->synth->AllVoices->GetAt(i);
voices += info->Id->Data();
voices += L":";
voices += info->DisplayName->Data();
if (i != instance->synth->AllVoices->Size - 1) {
voices += L"|";
}
}
return SysAllocString(voices.c_str());
}

const char16* __stdcall ocSpeech_getCurrentVoiceId(OcSpeech* instance) {
return instance->synth->Voice->Id->Data();
}

void __stdcall ocSpeech_setVoice(OcSpeech* instance, int index) {
instance->synth->Voice = instance->synth->AllVoices->GetAt(index);
}

const char16 * __stdcall ocSpeech_getCurrentVoiceLanguage(OcSpeech* instance) {
return instance->synth->Voice->Language->Data();
}
34 changes: 34 additions & 0 deletions nvdaHelper/localWin10/oneCoreSpeech.h
@@ -0,0 +1,34 @@
/*
Header for C dll bridge to Windows OneCore voices.
This file is a part of the NVDA project.
URL: http://www.nvaccess.org/
Copyright 2016-2017 Tyler Spivey, NV Access Limited.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This license can be found at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/

#pragma once
#define export __declspec(dllexport)

typedef void (*ocSpeech_Callback)(byte* data, int length, const char16* markers);
typedef struct {
Windows::Media::SpeechSynthesis::SpeechSynthesizer ^synth;
ocSpeech_Callback callback;
} OcSpeech;

extern "C" {
export OcSpeech* __stdcall ocSpeech_initialize();
export void __stdcall ocSpeech_terminate(OcSpeech* instance);
export void __stdcall ocSpeech_setCallback(OcSpeech* instance, ocSpeech_Callback fn);
export void __stdcall ocSpeech_speak(OcSpeech* instance, char16 *text);
export BSTR __stdcall ocSpeech_getVoices(OcSpeech* instance);
export const char16* __stdcall ocSpeech_getCurrentVoiceId(OcSpeech* instance);
export void __stdcall ocSpeech_setVoice(OcSpeech* instance, int index);
export const char16* __stdcall ocSpeech_getCurrentVoiceLanguage(OcSpeech* instance);
}
73 changes: 73 additions & 0 deletions nvdaHelper/localWin10/sconscript
@@ -0,0 +1,73 @@
###
#This file is a part of the NVDA project.
#URL: http://www.nvaccess.org/
#Copyright 2016-2017 NV Access Limited
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License version 2.0, as published by
#the Free Software Foundation.
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#This license can be found at:
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
###

import os

Import(
'env',
'sourceDir',
'libInstallDir',
'localLib',
)

TARGET_ARCH=env['TARGET_ARCH']
debug=env['nvdaHelperDebugFlags']
release=env['release']
signExec=env['signExec'] if env['certFile'] else None
progFilesX86 = os.getenv("ProgramFiles(x86)")

env.Append(CPPDEFINES=[
'UNICODE', '_CRT_SECURE_NO_DEPRECATE',
('LOGLEVEL','${nvdaHelperLogLevel}')])
env.Append(CCFLAGS=['/W3', '/WX'])
env.Append(CXXFLAGS=['/EHsc', '/ZW',
r'/AI%s\Microsoft Visual Studio 14.0\VC\vcpackages' % progFilesX86,
r'/AI%s\Windows Kits\10\UnionMetadata' % progFilesX86])
env.Append(CPPPATH=[Dir('..').abspath])
env.Append(LINKFLAGS=['/incremental:no', '/WX'])
env.Append(LINKFLAGS='/release') # We always want a checksum in the header

if not release:
env.Append(CCFLAGS=['/Od'])
else:
env.Append(CCFLAGS='/O2')
env.Append(CCFLAGS='/GL')
env.Append(LINKFLAGS=['/LTCG'])

if 'RTC' in debug:
env.Append(CCFLAGS=['/RTCsu'])

# We always want debug symbols
env.Append(PDB='${TARGET}.pdb')
env.Append(LINKFLAGS='/OPT:REF') #having symbols usually turns this off but we have no need for unused symbols

localWin10Lib = env.SharedLibrary(
target="nvdaHelperLocalWin10",
source=[
env['projectResFile'],
'oneCoreSpeech.cpp',
],
LIBS=["oleaut32", localLib[2]],
)
if signExec:
env.AddPostAction(localWin10Lib[0], [signExec])
env.Install(libInstallDir, localWin10Lib)

# UWP dlls can only be dynamically linked with the CRT,
# but some systems might not have this version of the CRT.
# Therefore, we must include it.
vcRedist = os.path.join(progFilesX86, r"Microsoft Visual Studio 14.0\VC\redist\onecore\x86\Microsoft.VC140.CRT")
for fn in ("msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll"):
fn = os.path.join(vcRedist, fn)
env.Install(sourceDir, fn)
19 changes: 19 additions & 0 deletions sconstruct
Expand Up @@ -152,9 +152,27 @@ env64=env.Clone(TARGET_ARCH='x86_64',tools=archTools)
# Hack around odd bug where some tool [after] msvc states that static and shared objects are different
env32['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
env64['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
# Environment for functionality that only works on Windows 10.
envWin10 = env.Clone(TARGET_ARCH='x86', tools=['default'])

env=env32

projectRCSubstDict={
'%version_year%':env['version_year'],
'%version_major%':env['version_major'],
'%version_minor%':env['version_minor'],
'%version_build%':env['version_build'],
'%copyright%':env['copyright'],
'%publisher%':env['publisher'],
'%version%':env['version'],
'%productName%':"%s (%s)"%(versionInfo.name,versionInfo.longName),
}
resFile=env.RES(target='build/nvda.res',
source=env.Substfile(target='build/nvda.rc', source='nvdaHelper/nvda.rc.subst', SUBST_DICT=projectRCSubstDict))
env32['projectResFile'] = resFile
env64['projectResFile'] = resFile
envWin10['projectResFile'] = resFile

#Fill sourceDir with anything provided for it by miscDeps
env.recursiveCopy(sourceDir,Dir('miscdeps/source'))

Expand All @@ -163,6 +181,7 @@ env.SConscript('source/comInterfaces_sconscript',exports=['env'])
#Process nvdaHelper scons files
env32.SConscript('nvdaHelper/archBuild_sconscript',exports={'env':env32,'clientInstallDir':clientDir.Dir('x86'),'libInstallDir':sourceLibDir},variant_dir='build/x86')
env64.SConscript('nvdaHelper/archBuild_sconscript',exports={'env':env64,'clientInstallDir':clientDir.Dir('x64'),'libInstallDir':sourceLibDir64},variant_dir='build/x86_64')
envWin10.SConscript('nvdaHelper/localWin10/sconscript', exports={'env': envWin10, 'libInstallDir': sourceLibDir}, variant_dir='build/x86/localWin10')

#Allow all NVDA's gettext po files to be compiled in source/locale
for po in env.Glob(sourceDir.path+'/locale/*/lc_messages/*.po'):
Expand Down
2 changes: 1 addition & 1 deletion source/speech.py
Expand Up @@ -1657,7 +1657,7 @@ def __init__(self,index):
def __repr__(self):
return "IndexCommand(%r)" % self.index

class CharacterModeCommand(object):
class CharacterModeCommand(SpeechCommand):
"""Turns character mode on and off for speech synths."""

def __init__(self,state):
Expand Down