|Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.|
How to Create a Sound InterfaceDescription: This tutorial covers how to create an interface for using sound_play. The interface uses a node acting as a 'funnel' as a convenient way to route sound commands through sound_play.
Keywords: sound, sound_play
Tutorial Level: INTERMEDIATE
This tutorial assumes that your linux sound drivers and speaker system are already working and that sound_play is working from the terminal, all of which can be tested in the Configuring and Using Speakers tutorial. The node created in this tutorial uses the sound_play convenience wrapper package to play sound. See the sound_play documentation for details.
Examples and References
The structure built in this tutorial will depend on sound_play and will require sound assets for testing.
One Method for Structuring the Interface
This structure routes all sound instructions, given in the form of yak commands, through a single node, here called yak_node. yak_node is then the sole node responsible for dealing with SoundClient() and sound_play, and acts as a 'funnel', allowing multiple requesters to play sounds.
SoundClient() requires full path names, and can also become overloaded and produce a segmentation fault. This partitioning of the structure allows both of these issues to be handled in a single node.
A suitable speaker command should communicate a file name, text-to-speech string, or built-in sound, and a command for type of input. Here, speaker commands have a string for a file name or for text-to-speech, and a command for playing the .wav file or for using text-to-speech. More command types could be added for playing .ogg or built-in files if needed.
Code and Explanation
Note: It is convenient to save all sound assets in the same directory, as SoundClient() requires full path names to play sound assets. SoundClient() also prefers all sound assets to be .wav or .ogg files.
All sound instructions in the form of yak commands are published to the yak topic, to which yak_node subscribes.
1 #!/usr/bin/env python 2 import roslib; roslib.load_manifest('sound_yak') 3 import rospy, os, sys 4 from sound_play.msg import SoundRequest 5 from sound_play.libsoundplay import SoundClient 6 from sound_yak.msg import yak_cmd 7 8 # directory with sound assets - change as needed 9 soundAssets = '/home/shiloh/devel/audio_assets/' 10 # duration of yak throttle 11 throttle = 3 # seconds 12 13 def sound_translator(data): 14 print data 15 global allow_yak 16 if rospy.Time.now() <= allow_yak: # Throttles yak to avoid 17 print("Sound throttled") # SoundClient segfault 18 return 19 # when to reallow yak 20 allow_yak = rospy.Time.now() + rospy.Duration.from_sec(throttle) 21 if data.cmd == "wav": 22 soundhandle.playWave(soundAssets + data.param) 23 if data.cmd == "say": 24 soundhandle.say(data.param) 25 26 def yak_init(): 27 rospy.init_node('yak_node', anonymous = True) 28 global allow_yak 29 allow_yak = rospy.Time.now() 30 rospy.Subscriber('yak', yak_cmd, sound_translator) 31 rospy.spin() 32 33 if __name__ == '__main__': 34 soundhandle = SoundClient() 35 rospy.sleep(1) 36 37 yak_init()
SoundClient() may produce a segmentation fault if too many sound commands are given in a short period of time, so sound commands are throttled in lines 14-20. Sounds can only be played when the current time is later than time allow_yak, and every time a sound is played, allow_yak is updated to the current time plus a throttle time, here 3 seconds, defined in line 11.
Lines 21-24 process yak commands. SoundClient()’s playWave requires full path names for sound assets. A convenient solution allows the parameter to be the file name only and concatenates the path name to the folder containing sound assets, as in line 22.
Publishing yak commands
Like any other ROS topic, yak commands are imported, set, and published. yak_cmd is imported as usual near the beginning of the file. yak.cmd and yak.param are both set before pub.publish(yak) in the callback method, then global yak = yak_cmd() and global pub = rospy.Publisher('yak', yak_cmd) are set in the initialization method.
The callback should contain:
The initializer should contain:
Running the interface
After a roscore is running, rosrun sound_play soundplay_node.py, and rosrun sound_yak yak_node.py (or whichever package you put it in) in a new tab. Also run your nodes that send yak commands. Here is a checklist for running yak_node:
Ensure that a roscore is running.
rosrun sound_play soundplay_node.py in a new tab.
$ rosrun sound_play soundplay_node.py
rosrun sound_yak yak_node.py (or whichever package you put it in) in a new tab.
$ rosrun sound_yak yak_node.py
Run your nodes that send yak commands.
$ rosrun my_pack my_node.py