<commit_before># Orca
#
# Copyright 2015 <NAME> <<EMAIL>>
# Copyright 2006, 2007, 2008, 2009 Brailcom, o.p.s.
#
# Author: <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.


"""Provides an Orca speech server for eSpeak backend."""

__id__ = "$Id$"
__version__   = "$Revision$"
__date__      = "$Date$"
__license__   = "LGPL"

from gi.repository import GLib
import re
import time
import os.path

from . import chnames
from . import debug
from . import guilabels
from . import messages
from . import speechserver
from . import settings
from . import orca_state
from . import punctuation_settings
from .acss import ACSS

try:
    from espeak import espeak
except:
    _espeak_available = False
else:    
    _espeak_available = True

PUNCTUATION = re.compile('[^\w\s]', re.UNICODE)
ELLIPSIS = re.compile('(\342\200\246|\.\.\.\s*)')

#Parameter bounds
minRate=80
maxRate=450
minPitch=0
maxPitch=99

class SpeechServer(speechserver.SpeechServer):
    # See the parent class for documentation.

    _active_servers = {}
    
    DEFAULT_SERVER_ID = 'default'
    _SERVER_NAMES = {DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER}

    def getFactoryName():
        return guilabels.ESPEAK
    getFactoryName = staticmethod(getFactoryName)

    def getSpeechServers():
        servers = []
        default = SpeechServer._getSpeechServer(SpeechServer.DEFAULT_SERVER_ID)
        if default is not None:
            servers.append(default)
        return servers
    getSpeechServers = staticmethod(getSpeechServers)

    def _getSpeechServer(cls, serverId):
        """Return an active server for given id.

        Attempt to create the server if it doesn't exist yet.  Returns None
        when it is not possible to create the server.
        
        """
        if serverId not in cls._active_servers:
            cls(serverId)
        # Don't return the instance, unless it is succesfully added
        # to `_active_Servers'.
        return cls._active_servers.get(serverId)
    _getSpeechServer = classmethod(_getSpeechServer)

    def getSpeechServer(info=None):
        if info is not None:
            thisId = info[1]
        else:
            thisId = SpeechServer.DEFAULT_SERVER_ID
        return SpeechServer._getSpeechServer(thisId)
    getSpeechServer = staticmethod(getSpeechServer)

    def shutdownActiveServers():
        for server in list(SpeechServer._active_servers.values()):
            server.shutdown()
    shutdownActiveServers = staticmethod(shutdownActiveServers)

    # *** Instance methods ***

    def __init__(self, serverId):
        super(SpeechServer, self).__init__()
        self._id = serverId
        self._client = None
        self._current_voice_properties = {}
        self._acss_manipulators = (
            (ACSS.RATE, self._set_rate),
            (ACSS.AVERAGE_PITCH, self._set_pitch),
            (ACSS.GAIN, self._set_volume),
            (ACSS.FAMILY, self._set_family),
            )
        if not _espeak_available:
            debug.println(debug.LEVEL_WARNING,
                          "eSpeak interface not installed.")
            return
        self._PUNCTUATION_MODE_MAP = {
            settings.PUNCTUATION_STYLE_ALL:  espeak.Punctuation.All,
            settings.PUNCTUATION_STYLE_MOST: espeak.Punctuation.Custom,
            settings.PUNCTUATION_STYLE_SOME: espeak.Punctuation.Custom,
            settings.PUNCTUATION_STYLE_NONE: espeak.Punctuation.Any,
            }
        self._CALLBACK_TYPE_MAP = {
            espeak.event_SENTENCE: speechserver.SayAllContext.PROGRESS,
            #espeak.event_END: speechserver.SayAllContext.INTERRUPTED,
            espeak.event_MSG_TERMINATED: speechserver.SayAllContext.COMPLETED,
           #espeak.event_MARK:speechserver.SayAllContext.PROGRESS,
            }

        self._default_voice_name = guilabels.SPEECH_DEFAULT_VOICE % serverId
        
        try:
            self._init()
        except:
            debug.println(debug.LEVEL_WARNING,
                          "eSpeak failed to initialize:")
            debug.printException(debug.LEVEL_WARNING)
        else:
            SpeechServer._active_servers[serverId] = self

        self._lastKeyEchoTime = None

    def _init(self):
        self._current_voice_properties = {}
        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)

    def updateCapitalizationStyle(self):
        """Updates the capitalization style used by the speech server."""
        pass

    def updatePunctuationLevel(self):
        """ Punctuation level changed, inform this speechServer. """
        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)

    def _paramToPercent(self, current, min, max):
        """Convert a raw parameter value to a percentage given the current, minimum and maximum raw values.
        @param current: The current value.
        @type current: int
        @param min: The minimum value.
        @type current: int
        @param max: The maximum value.
        @type max: int
        """
        return int(round(float(current - min) / (max - min) * 100))

    def _percentToParam(self, percent, min, max):
        """Convert a percentage to a raw parameter value given the current percentage and the minimum and maximum raw parameter values.
        @param percent: The current percentage.
        @type percent: int
        @param min: The minimum raw parameter value.
        @type min: int
        @param max: The maximum raw parameter value.
        @type max: int
        """
        return int(round(float(percent) / 100 * (max - min) + min))

    def _set_rate(self, acss_rate):
        rate = self._percentToParam(acss_rate, minRate, maxRate)
        espeak.set_parameter(espeak.Parameter.Rate, rate, 0)

    def _set_pitch(self, acss_pitch):
        pitch = self._percentToParam((acss_pitch *10), minPitch, maxPitch)
        espeak.set_parameter(espeak.Parameter.Pitch, pitch, 0)

    def _set_volume(self, acss_volume):
        volume = int(acss_volume *10)
        espeak.set_parameter(espeak.Parameter.Volume, volume, 0)

    def _set_family(self, acss_family):<commit_msg>espeakfactory: Use proper volume range from 0 to 200 so louder eSpeak volume than the default is possible.<commit_after># Orca
#
# Copyright 2015 <NAME> <<EMAIL>>
# Copyright 2006, 2007, 2008, 2009 Brailcom, o.p.s.
#
# Author: <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.


"""Provides an Orca speech server for eSpeak backend."""

__id__ = "$Id$"
__version__   = "$Revision$"
__date__      = "$Date$"
__license__   = "LGPL"

from gi.repository import GLib
import re
import time

from . import chnames
from . import debug
from . import guilabels
from . import messages
from . import speechserver
from . import settings
from . import orca_state
from . import punctuation_settings
from .acss import ACSS

try:
    from espeak import espeak
except:
    _espeak_available = False
else:    
    _espeak_available = True

PUNCTUATION = re.compile('[^\w\s]', re.UNICODE)
ELLIPSIS = re.compile('(\342\200\246|\.\.\.\s*)')

#Parameter bounds
minRate=80
maxRate=450
minPitch=0
maxPitch=99
minVolume=0
maxVolume=200

class SpeechServer(speechserver.SpeechServer):
    # See the parent class for documentation.

    _active_servers = {}
    
    DEFAULT_SERVER_ID = 'default'
    _SERVER_NAMES = {DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER}

    def getFactoryName():
        return guilabels.ESPEAK
    getFactoryName = staticmethod(getFactoryName)

    def getSpeechServers():
        servers = []
        default = SpeechServer._getSpeechServer(SpeechServer.DEFAULT_SERVER_ID)
        if default is not None:
            servers.append(default)
        return servers
    getSpeechServers = staticmethod(getSpeechServers)

    def _getSpeechServer(cls, serverId):
        """Return an active server for given id.

        Attempt to create the server if it doesn't exist yet.  Returns None
        when it is not possible to create the server.
        
        """
        if serverId not in cls._active_servers:
            cls(serverId)
        # Don't return the instance, unless it is succesfully added
        # to `_active_Servers'.
        return cls._active_servers.get(serverId)
    _getSpeechServer = classmethod(_getSpeechServer)

    def getSpeechServer(info=None):
        if info is not None:
            thisId = info[1]
        else:
            thisId = SpeechServer.DEFAULT_SERVER_ID
        return SpeechServer._getSpeechServer(thisId)
    getSpeechServer = staticmethod(getSpeechServer)

    def shutdownActiveServers():
        for server in list(SpeechServer._active_servers.values()):
            server.shutdown()
    shutdownActiveServers = staticmethod(shutdownActiveServers)

    # *** Instance methods ***

    def __init__(self, serverId):
        super(SpeechServer, self).__init__()
        self._id = serverId
        self._client = None
        self._current_voice_properties = {}
        self._acss_manipulators = (
            (ACSS.RATE, self._set_rate),
            (ACSS.AVERAGE_PITCH, self._set_pitch),
            (ACSS.GAIN, self._set_volume),
            (ACSS.FAMILY, self._set_family),
            )
        if not _espeak_available:
            debug.println(debug.LEVEL_WARNING,
                          "eSpeak interface not installed.")
            return
        self._PUNCTUATION_MODE_MAP = {
            settings.PUNCTUATION_STYLE_ALL:  espeak.Punctuation.All,
            settings.PUNCTUATION_STYLE_MOST: espeak.Punctuation.Custom,
            settings.PUNCTUATION_STYLE_SOME: espeak.Punctuation.Custom,
            settings.PUNCTUATION_STYLE_NONE: espeak.Punctuation.Any,
            }
        self._CALLBACK_TYPE_MAP = {
            espeak.event_SENTENCE: speechserver.SayAllContext.PROGRESS,
            #espeak.event_END: speechserver.SayAllContext.INTERRUPTED,
            espeak.event_MSG_TERMINATED: speechserver.SayAllContext.COMPLETED,
           #espeak.event_MARK:speechserver.SayAllContext.PROGRESS,
            }

        self._default_voice_name = guilabels.SPEECH_DEFAULT_VOICE % serverId
        
        try:
            self._init()
        except:
            debug.println(debug.LEVEL_WARNING,
                          "eSpeak failed to initialize:")
            debug.printException(debug.LEVEL_WARNING)
        else:
            SpeechServer._active_servers[serverId] = self

        self._lastKeyEchoTime = None

    def _init(self):
        self._current_voice_properties = {}
        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)

    def updateCapitalizationStyle(self):
        """Updates the capitalization style used by the speech server."""
        pass

    def updatePunctuationLevel(self):
        """ Punctuation level changed, inform this speechServer. """
        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)

    def _paramToPercent(self, current, min, max):
        """Convert a raw parameter value to a percentage given the current, minimum and maximum raw values.
        @param current: The current value.
        @type current: int
        @param min: The minimum value.
        @type current: int
        @param max: The maximum value.
        @type max: int
        """
        return int(round(float(current - min) / (max - min) * 100))

    def _percentToParam(self, percent, min, max):
        """Convert a percentage to a raw parameter value given the current percentage and the minimum and maximum raw parameter values.
        @param percent: The current percentage.
        @type percent: int
        @param min: The minimum raw parameter value.
        @type min: int
        @param max: The maximum raw parameter value.
        @type max: int
        """
        return int(round(float(percent) / 100 * (max - min) + min))

    def _set_rate(self, acss_rate):
        rate = self._percentToParam(acss_rate, minRate, maxRate)
        espeak.set_parameter(espeak.Parameter.Rate, rate, 0)

    def _set_pitch(self, acss_pitch):
        pitch = self._percentToParam((acss_pitch *10), minPitch, maxPitch)
        espeak.set_parameter(espeak.Parameter.Pitch, pitch, 0)

    def _set_volume(self, acss_volume):
        volume = self._percentToParam((acss_volume *10), minVolume, maxVolume)
        espeak.set_parameter(espeak.Parameter.Volume, volume, 0)

    def _set_family(self, acss_family):
{'fraction_non_alphanumeric': 0.08342261267052353, 'fraction_numerical': 0.009713591886293836, 'mean_word_length': 2.7997286295793757, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5143097', 'n_tokens_mistral': 4284, 'n_tokens_neox': 4055, 'n_words': 1128}