diff options
Diffstat (limited to 'Speak.activity/activity.py')
-rwxr-xr-x | Speak.activity/activity.py | 275 |
1 files changed, 247 insertions, 28 deletions
diff --git a/Speak.activity/activity.py b/Speak.activity/activity.py index 629219d..fbe96ad 100755 --- a/Speak.activity/activity.py +++ b/Speak.activity/activity.py @@ -1,23 +1,53 @@ -# coding: UTF8 +# Speak.activity +# A simple front end to the espeak text-to-speech engine on the XO laptop +# +# 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. +# +# Foobar 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 Foobar. If not, see <http://www.gnu.org/licenses/>. -import commands, subprocess + +import sys +import os +import subprocess import random from sugar.activity import activity from sugar.datastore import datastore from sugar.presence import presenceservice import logging -import sys, os import gtk import gobject import pango +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toolcombobox import ToolComboBox +from sugar.graphics.combobox import ComboBox + import pygst pygst.require("0.10") import gst import audio import eye +import glasses import mouth +import fft_mouth +import waveform_mouth +import voice class SpeakActivity(activity.Activity): def __init__(self, handle): @@ -25,68 +55,257 @@ class SpeakActivity(activity.Activity): activity.Activity.__init__(self, handle) bounds = self.get_allocation() - toolbox = activity.ActivityToolbox(self) - self.set_toolbox(toolbox) - toolbox.show() + # pick a voice that espeak supports + self.voices = voice.allVoices() + #self.voice = random.choice(self.voices.values()) + self.voice = self.voices["Default"] + # make an audio device for playing back and rendering audio + self.active = False + self.connect( "notify::active", self._activeCb ) + self.audio = audio.AudioGrab(datastore, self._jobject) + + #self.proc = None + + # make a box to type into self.entry = gtk.Entry() self.entry.set_editable(True) self.entry.connect('activate', self.entry_activate_cb) self.input_font = pango.FontDescription(str='sans bold 24') self.entry.modify_font(self.input_font) - self.eyes = [eye.Eye(), eye.Eye()] - eyeBox = gtk.HBox() - eyeBox.pack_start(self.eyes[0]) - eyeBox.pack_start(self.eyes[1]) - map(lambda e: e.set_size_request(300,300), self.eyes) + # make an empty box for some eyes + self.eyes = None + self.eyebox = gtk.HBox() - self.ACTIVE = True - self.connect( "notify::active", self._activeCb ) - self.audio = audio.AudioGrab(datastore, self._jobject) - self.mouth = mouth.Mouth(self.audio) + # make an empty box to put the mouth in + self.mouth = None + self.mouthbox = gtk.HBox() + # layout the screen box = gtk.VBox(homogeneous=False) - box.pack_start(eyeBox, expand=False) - box.pack_start(self.mouth) + box.pack_start(self.eyebox, expand=False) + box.pack_start(self.mouthbox) box.pack_start(self.entry, expand=False) self.set_canvas(box) box.show_all() + box.add_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.POINTER_MOTION_MASK) + box.connect("motion_notify_event", self._mouse_moved_cb) + box.connect("button_press_event", self._mouse_clicked_cb) + + # make some toolbars + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + toolbox.show() + #activitybar = toolbox.get_activity_toolbar() + + voicebar = self.make_voice_bar() + toolbox.add_toolbar("Voice", voicebar) + voicebar.show() + + facebar = self.make_face_bar() + toolbox.add_toolbar("Face", facebar) + facebar.show() + + # make the text box active right away self.entry.grab_focus() - gobject.timeout_add(100, self._timeout_cb) + # start polling for audio + #gobject.timeout_add(100, self._timeout_cb) + # say hello to the user + self.active = True presenceService = presenceservice.get_instance() xoOwner = presenceService.get_owner() - self.say("Hi %s, my name is Otto. Type something." % xoOwner.props.nick) + self.say("Hello %s, my name is XO. Type something." % xoOwner.props.nick) + + def _mouse_moved_cb(self, widget, event): + map(lambda w: w.queue_draw(), self.eyes) + + def _mouse_clicked_cb(self, widget, event): + pass + + def make_voice_bar(self): + voicebar = gtk.Toolbar() + + # button = ToolButton('change-voice') + # button.set_tooltip("Change Voice") + # button.connect('clicked', self.change_voice_cb) + # voicebar.insert(button, -1) + # button.show() + + combo = ComboBox() + combo.connect('changed', self.voice_changed_cb) + voicenames = self.voices.keys() + voicenames.sort() + for name in voicenames: + combo.append_item(self.voices[name], name) + combo.set_active(voicenames.index(self.voice.friendlyname)) + combotool = ToolComboBox(combo) + voicebar.insert(combotool, -1) + combotool.show() + + self.pitchadj = gtk.Adjustment(50, 0, 99, 1, 10, 0) + self.pitchadj.connect("value_changed", self.pitch_adjusted_cb, self.pitchadj) + 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) + pitchtool = gtk.ToolItem() + pitchtool.add(pitchbar) + pitchtool.show() + voicebar.insert(pitchtool, -1) + pitchbar.show() + + self.rateadj = gtk.Adjustment(100, 80, 370, 1, 10, 0) + self.rateadj.connect("value_changed", self.rate_adjusted_cb, self.rateadj) + 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) + ratetool = gtk.ToolItem() + ratetool.add(ratebar) + ratetool.show() + voicebar.insert(ratetool, -1) + ratebar.show() + + return voicebar + + def voice_changed_cb(self, combo): + self.voice = combo.props.value + self.say(self.voice.friendlyname) + + def pitch_adjusted_cb(self, get, data=None): + self.say("pitch adjusted") + + def rate_adjusted_cb(self, get, data=None): + self.say("rate adjusted") + + + def make_face_bar(self): + facebar = gtk.Toolbar() + + self.numeyesadj = None + + # button = ToolButton('change-voice') + # button.set_tooltip("Change Voice") + # button.connect('clicked', self.change_voice_cb) + # facebar.insert(button, -1) + # button.show() + + combo = ComboBox() + combo.connect('changed', self.mouth_changed_cb) + combo.append_item(mouth.Mouth, "Simple") + combo.append_item(waveform_mouth.WaveformMouth, "Waveform") + combo.append_item(fft_mouth.FFTMouth, "Frequency") + combo.set_active(0) + combotool = ToolComboBox(combo) + facebar.insert(combotool, -1) + combotool.show() + + self.eye_shape_combo = ComboBox() + self.eye_shape_combo.connect('changed', self.eyes_changed_cb) + self.eye_shape_combo.append_item(eye.Eye, "Round") + self.eye_shape_combo.append_item(glasses.Glasses, "Glasses") + combotool = ToolComboBox(self.eye_shape_combo) + facebar.insert(combotool, -1) + combotool.show() + + self.numeyesadj = gtk.Adjustment(2, 1, 5, 1, 1, 0) + self.numeyesadj.connect("value_changed", self.eyes_changed_cb, self.numeyesadj) + numeyesbar = gtk.HScale(self.numeyesadj) + numeyesbar.set_draw_value(False) + numeyesbar.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + numeyesbar.set_size_request(240,15) + numeyestool = gtk.ToolItem() + numeyestool.add(numeyesbar) + numeyestool.show() + facebar.insert(numeyestool, -1) + numeyesbar.show() + + self.eye_shape_combo.set_active(0) + + return facebar + + def mouth_changed_cb(self, combo): + mouth_class = combo.props.value + if self.mouth: + self.mouthbox.remove(self.mouth) + self.mouth = mouth_class(self.audio) + self.mouthbox.add(self.mouth) + self.mouth.show() + # enable mouse move events so we can track the eyes while the mouse is over the mouth + self.mouth.add_events(gtk.gdk.POINTER_MOTION_MASK) + # this SegFaults: self.say(combo.get_active_text()) + self.say("mouth changed") + + def eyes_changed_cb(self, ignored, ignored2=None): + if self.numeyesadj is None: + return + + eye_class = self.eye_shape_combo.props.value + if self.eyes: + for eye in self.eyes: + self.eyebox.remove(eye) + + self.eyes = [] + numberOfEyes = int(self.numeyesadj.value) + for i in range(numberOfEyes): + eye = eye_class() + self.eyes.append(eye) + self.eyebox.pack_start(eye) + eye.set_size_request(300,300) + eye.show() + + # this SegFaults: self.say(self.eye_shape_combo.get_active_text()) + self.say("eyes changed") def _timeout_cb(self): - self.mouth.queue_draw(); + # make the mouth update with the latest waveform + # ideally we would only do this when the audio is actually playing + if self.mouth: + self.mouth.queue_draw(); return True def entry_activate_cb(self, entry): + # the user pressed Return, say the text and clear it out text = entry.props.text if text: self.say(text) + # ideally we would clear it after we finish saying it + # so that you would be able to compare the audio to the text + # without having to remember what you typed entry.props.text = '' - def speak(self, widget, data=None): - self.say(random.choice(["Let's go to Annas","Hi Opal, how are you?"])) - def say(self, something): + if self.audio is None or not self.active: + return + # ideally we would stream the audio instead of writing to disk each time... + print self.voice.name, ":", something wavpath = "/tmp/speak.wav" - subprocess.call(["espeak", "-w", wavpath, something]) - #subprocess.call(["playwave", wavpath]) + subprocess.call(["espeak", "-w", wavpath, "-p", str(self.pitchadj.value), "-s", str(self.rateadj.value), "-v", self.voice.name, something], stdout=subprocess.PIPE) self.audio.playfile(wavpath) + # this doesn't seem to work, but would avoid the /tmp/file.wave + # if self.proc: + # self.proc = None + # self.proc = subprocess.Popen(["espeak", "--stdout", "-s", "100", "-v", self.voice.name, something], stdout=subprocess.PIPE) + # print self.proc + # print self.proc.stdout + # print self.proc.stdout.fileno() + # self.audio.playfd(self.proc.stdout.fileno()) def _activeCb( self, widget, pspec ): - if (not self.props.active and self.ACTIVE): + # only generate sound when this activity is active + if (not self.props.active and self.active): self.audio.stop_sound_device() - elif (self.props.active and not self.ACTIVE): + elif (self.props.active and not self.active): self.audio.restart_sound_device() - self.ACTIVE = self.props.active + self.active = self.props.active def on_quit(self, data=None): self.audio.on_quit() |