# Speak.activity # A simple front end to the espeak text-to-speech engine on the XO laptop # http://wiki.laptop.org/go/Speak # # Copyright (C) 2008 Joshua Minor # This file is part of Speak.activity # # Parts of Speak.activity are based on code from Measure.activity # Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org # # Speak.activity is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Speak.activity 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Speak.activity. If not, see . from sugar3.activity import activity from sugar3.presence import presenceservice import logging from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GObject from gi.repository import Pango import json from gettext import gettext as _ from sugar3.graphics.toolbutton import ToolButton from sugar3.graphics.toggletoolbutton import ToggleToolButton from sugar3.graphics.radiotoolbutton import RadioToolButton from toolkit.toolitem import ToolWidget from toolkit.combobox import ComboBox # from toolkit.toolbarbox import ToolbarBox from sugar3.graphics.toolbarbox import ToolbarBox from toolkit.activity import SharedActivity from toolkit.activity_widgets import * import eye import glasses import mouth import fft_mouth import waveform_mouth import voice import face import brain #import chat import espeak from messenger import Messenger, SERVICE logger = logging.getLogger('speak') MODE_TYPE = 1 MODE_BOT = 2 MODE_CHAT = 3 class SpeakActivity(SharedActivity): def __init__(self, handle): self.notebook = Gtk.Notebook() SharedActivity.__init__(self, self.notebook, SERVICE, handle) self._mode = MODE_TYPE self.numeyesadj = None # make an audio device for playing back and rendering audio self.connect("notify::active", self._activeCb) # make a box to type into self.entrycombo = Gtk.ComboBoxText() self.entrycombo.connect("changed", self._combo_changed_cb) self.entry = self.entrycombo.get_child() # self.entry.set_editable(True) # self.entry.connect('activate', self._entry_activate_cb) self.entry.connect("key-press-event", self._entry_key_press_cb) self.input_font = Pango.FontDescription('sans bold 24') self.entry.modify_font(self.input_font) self.face = face.View() self.face.show() # layout the screen box = Gtk.VBox(False, 0) box.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) box.add(self.face) box.add(self.entrycombo) self.add_events(Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.POINTER_MOTION_MASK) self.connect("motion_notify_event", self._mouse_moved_cb) box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) box.connect("button_press_event", self._mouse_clicked_cb) # desktop self.notebook.show() self.notebook.props.show_border=False self.notebook.props.show_tabs=False box.show_all() self.notebook.append_page(box, None) """ self.chat = chat.View() self.chat.show_all() self.notebook.append_page(self.chat) """ # make the text box active right away self.entry.grab_focus() # self.entry.connect("move-cursor", self._cursor_moved_cb) # self.entry.connect("changed", self._cursor_moved_cb) # toolbar toolbox = ToolbarBox() toolbox.toolbar.insert(ActivityToolbarButton(self), -1) self.voices = ComboBox() for name in sorted(voice.allVoices().keys()): vn = voice.allVoices()[name] n = name [ : 26 ] + ".." self.voices.append_item(vn, n) self.voices.select(voice.defaultVoice()) all_voices = self.voices.get_model() brain_voices = brain.get_voices() mode_type = RadioToolButton( named_icon='mode-type', tooltip=_('Type something to hear it')) mode_type.connect('toggled', self.__toggled_mode_type_cb, all_voices) toolbox.toolbar.insert(mode_type, -1) mode_robot = RadioToolButton( named_icon='mode-robot', group=mode_type, tooltip=_('Ask robot any question')) mode_robot.connect('toggled', self.__toggled_mode_robot_cb, brain_voices) toolbox.toolbar.insert(mode_robot, -1) mode_chat = RadioToolButton( named_icon='mode-chat', group=mode_type, tooltip=_('Voice chat')) mode_chat.connect('toggled', self.__toggled_mode_chat_cb, all_voices) toolbox.toolbar.insert(mode_chat, -1) voices_toolitem = ToolWidget(widget=self.voices) toolbox.toolbar.insert(voices_toolitem, -1) voice_button = ToolbarButton( page=self.make_voice_bar(), label=_('Voice'), icon_name='voice') toolbox.toolbar.insert(voice_button, -1) face_button = ToolbarButton( page=self.make_face_bar(), label=_('Face'), icon_name='face') toolbox.toolbar.insert(face_button, -1) separator = Gtk.SeparatorToolItem() separator.set_draw(False) separator.set_expand(True) toolbox.toolbar.insert(separator, -1) toolbox.toolbar.insert(StopButton(self), -1) toolbox.show_all() self.toolbar_box = toolbox def new_instance(self): self.voices.connect('changed', self.__changed_voices_cb) self.pitchadj.connect("value_changed", self.pitch_adjusted_cb, self.pitchadj) self.rateadj.connect("value_changed", self.rate_adjusted_cb, self.rateadj) self.mouth_shape_combo.connect('changed', self.mouth_changed_cb, False) self.mouth_changed_cb(self.mouth_shape_combo, True) self.numeyesadj.connect("value_changed", self.eyes_changed_cb, False) self.eye_shape_combo.connect('changed', self.eyes_changed_cb, False) self.eyes_changed_cb(None, True) self.face.look_ahead() # say hello to the user presenceService = presenceservice.get_instance() xoOwner = presenceService.get_owner() self.face.say_notification(_("Hello %s. Please Type something.") \ % xoOwner.props.nick) def resume_instance(self, file_path): cfg = json.loads(file(file_path, 'r').read()) status = self.face.status = face.Status().deserialize(cfg['status']) self.voices.select(status.voice) self.pitchadj.value = self.face.status.pitch self.rateadj.value = self.face.status.rate self.mouth_shape_combo.select(status.mouth) self.eye_shape_combo.select(status.eyes[0]) self.numeyesadj.value = len(status.eyes) self.entry.props.text = cfg['text'].encode('utf-8', 'ignore') for i in cfg['history']: self.entrycombo.append_text(i.encode('utf-8', 'ignore')) self.new_instance() def save_instance(self, file_path): cfg = {'status': self.face.status.serialize(), 'text': unicode(self.entry.props.text, 'utf-8', 'ignore'), 'history': [unicode(i[0], 'utf-8', 'ignore') \ for i in self.entrycombo.get_model()], } file(file_path, 'w').write(json.dumps(cfg)) """ def share_instance(self, connection, is_initiator): self.chat.messenger = Messenger(connection, is_initiator, self.chat) """ def _cursor_moved_cb(self, entry, *ignored): # make the eyes track the motion of the text cursor index = entry.props.cursor_position layout = entry.get_layout() pos = layout.get_cursor_pos(index) x = pos[0][0] / Pango.SCALE - entry.props.scroll_offset y = entry.get_allocation().y self.face.look_at(pos=(x, y)) def get_mouse(self): display = Gdk.Display.get_default() screen, mouseX, mouseY, modifiers = display.get_pointer() return mouseX, mouseY def _mouse_moved_cb(self, widget, event): # make the eyes track the motion of the mouse cursor self.face.look_at() # self.chat.look_at() def _mouse_clicked_cb(self, widget, event): pass def make_voice_bar(self): voicebar = Gtk.Toolbar() self.pitchadj = Gtk.Adjustment(self.face.status.pitch, 0, espeak.PITCH_MAX, 1, espeak.PITCH_MAX/10, 0) pitchbar = Gtk.HScale() # self.pitchadj) pitchbar.set_draw_value(False) # pitchbar.set_inverted(True) # pitchbar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS) pitchbar.set_size_request(240, 15) pitchbar_toolitem = ToolWidget( widget=pitchbar, label_text=_('Pitch:')) voicebar.insert(pitchbar_toolitem, -1) self.rateadj = Gtk.Adjustment(self.face.status.rate, 0, espeak.RATE_MAX, 1, espeak.RATE_MAX / 10, 0) ratebar = Gtk.HScale()# self.rateadj) ratebar.set_draw_value(False) # ratebar.set_inverted(True) # ratebar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS) ratebar.set_size_request(240, 15) ratebar_toolitem = ToolWidget( widget=ratebar, label_text=_('Rate:')) voicebar.insert(ratebar_toolitem, -1) voicebar.show_all() return voicebar def pitch_adjusted_cb(self, get, data=None): self.face.status.pitch = get.value self.face.say_notification(_("pitch adjusted")) def rate_adjusted_cb(self, get, data=None): self.face.status.rate = get.value self.face.say_notification(_("rate adjusted")) def make_face_bar(self): facebar = Gtk.Toolbar() self.mouth_shape_combo = ComboBox() self.mouth_shape_combo.append_item(mouth.Mouth, _("Simple")) self.mouth_shape_combo.append_item(waveform_mouth.WaveformMouth, _("Waveform")) self.mouth_shape_combo.append_item(fft_mouth.FFTMouth, _("Frequency")) self.mouth_shape_combo.set_active(0) mouth_shape_toolitem = ToolWidget( widget=self.mouth_shape_combo, label_text=_('Mouth:')) facebar.insert(mouth_shape_toolitem, -1) self.eye_shape_combo = ComboBox() self.eye_shape_combo.append_item(eye.Eye, _("Round")) self.eye_shape_combo.append_item(glasses.Glasses, _("Glasses")) self.eye_shape_combo.set_active(0) eye_shape_toolitem = ToolWidget( widget=self.eye_shape_combo, label_text=_('Eyes:')) facebar.insert(eye_shape_toolitem, -1) self.numeyesadj = Gtk.Adjustment(2, 1, 5, 1, 1, 0) numeyesbar = Gtk.HScale() #self.numeyesadj) # numeyesbar.set_draw_value(False) # numeyesbar.set_update_policy(Gtk.UPDATE_DISCONTINUOUS) numeyesbar.set_size_request(240, 15) numeyesbar_toolitem = ToolWidget( widget=numeyesbar, label_text=_('Eyes number:')) facebar.insert(numeyesbar_toolitem, -1) facebar.show_all() return facebar def mouth_changed_cb(self, combo, quiet): self.face.status.mouth = combo.props.value self._update_face() # this SegFaults: self.face.say(combo.get_active_text()) if not quiet: self.face.say_notification(_("mouth changed")) def eyes_changed_cb(self, ignored, quiet): if self.numeyesadj is None: return self.face.status.eyes = [self.eye_shape_combo.props.value] \ * int(self.numeyesadj.value) self._update_face() # this SegFaults: self.face.say(self.eye_shape_combo.get_active_text()) if not quiet: self.face.say_notification(_("eyes changed")) def _update_face(self): self.face.update() # self.chat.update(self.face.status) def _combo_changed_cb(self, combo): # when a new item is chosen, make sure the text is selected if not self.entry.is_focus(): self.entry.grab_focus() self.entry.select_region(0, -1) def _entry_key_press_cb(self, combo, event): # make the up/down arrows navigate through our history keyname = Gdk.keyval_name(event.keyval) if keyname == "Up": index = self.entrycombo.get_active() if index>0: index-=1 self.entrycombo.set_active(index) self.entry.select_region(0,-1) return True elif keyname == "Down": index = self.entrycombo.get_active() if index20: self.entrycombo.remove_text(0) # select the new item self.entrycombo.set_active(len(history)-1) # select the whole text entry.select_region(0, -1) def _activeCb(self, widget, pspec): # only generate sound when this activity is active if not self.props.active: self.face.shut_up() # self.chat.shut_up() def _set_voice(self, new_voice): try: self.voices.handler_block_by_func(self.__changed_voices_cb) self.voices.select(new_voice) self.face.status.voice = new_voice finally: self.voices.handler_unblock_by_func(self.__changed_voices_cb) def __toggled_mode_type_cb(self, button, voices_model): if not button.props.active: return self._mode = MODE_TYPE # self.chat.shut_up() self.face.shut_up() self.notebook.set_current_page(0) old_voice = self.voices.props.value self.voices.set_model(voices_model) self._set_voice(old_voice) def __toggled_mode_robot_cb(self, button, voices_model): if not button.props.active: return self._mode = MODE_BOT # self.chat.shut_up() self.face.shut_up() self.notebook.set_current_page(0) old_voice = self.voices.props.value self.voices.set_model(voices_model) new_voice = [i[0] for i in voices_model if i[0].short_name == old_voice.short_name] if not new_voice: new_voice = brain.get_default_voice() sorry = _("Sorry, I can't speak %(old_voice)s, " \ "let's talk %(new_voice)s instead.") % { 'old_voice': old_voice.friendlyname, 'new_voice': new_voice.friendlyname, } else: new_voice = new_voice[0] sorry = None self._set_voice(new_voice) if not brain.load(self, self.voices.props.value, sorry): if sorry: self.face.say_notification(sorry) def __toggled_mode_chat_cb(self, button, voices_model): if not button.props.active: return # is_first_session = not self.chat.me.get_mapped() self._mode = MODE_CHAT self.face.shut_up() self.notebook.set_current_page(1) old_voice = self.voices.props.value self.voices.set_model(voices_model) self._set_voice(old_voice) """ if is_first_session: self.chat.me.say_notification( _("You are in off-line mode, share and invite someone.")) """ def __changed_voices_cb(self, combo): voice = combo.props.value self.face.set_voice(voice) if self._mode == MODE_BOT: brain.load(self, voice) # activate Gtk threads when this module loads Gdk.threads_init()