def playMusic(self, songName, volume=None, playheadTime=0.0, timeReference=TimeReference.ABSOLUTE, numRepeats=0, repeatPeriod=0):
        '''
        Play a piece of music (a sound file) at the SpeakEasy node.
        @param songName: Name of sound file. (See getSongNames()). 
        @type songName: string
        @param volume: Loudness at which to play. Default uses current volume. Else must be in range 0.0 to 1.0
        @type volume: {None | float}
        @param playheadTime: Offset in fractional seconds into where to start the song. Default: start at beginning. 
        @type playheadTime: float
        @param timeReference: If playheadTime is provided, specifies whether the given time is intended as absolute (relative to the
                              beginning of the song), or relative to the current playhead position.
        @type timeReference: TimeReference
        @param numRepeats: Number of times the song is to be repeated after the first time. Use -1 to play forever.
        @type numRepeats: int
        @param repeatPeriod: Time period in fractional seconds to wait between repeats.
        @type repeatPeriod: float
        @raise TypeError: if any parameters are of incorrect type. 
        '''
        
        if not SpeakeasyUtils.ensureType(numRepeats, int):
            raise TypeError("Number of repeats must be an integer. Was " + str(numRepeats));
        if not SpeakeasyUtils.ensureType(repeatPeriod, int) and not SpeakeasyUtils.ensureType(repeatPeriod, float):
            raise TypeError("Repeat period must be an integer or a float. Was " + str(repeatPeriod));
        if not SpeakeasyUtils.ensureType(playheadTime, int) and not SpeakeasyUtils.ensureType(playheadTime, float):
            raise TypeError("Playhead must be an integer or a float. Was " + str(playheadTime));
        if (timeReference != TimeReference.ABSOLUTE) and (timeReference != TimeReference.RELATIVE):
            raise TypeError("Time reference must be TimeReference.RELATIVE, or TimeReference.ABSOLUTE. Was " + str(timeReference));
        
        musicReqMsg = SpeakEasyMusic();
        musicReqMsg.command = MusicCommands.PLAY;
        musicReqMsg.song_name = songName;
        musicReqMsg.time = playheadTime;
        musicReqMsg.timeReference = timeReference;
        if volume is None:
            musicReqMsg.volume = -1.0;
        else:
            musicReqMsg.volume = volume;

        with self.musicLock:
            self.rosMusicRequestor.publish(musicReqMsg);
            
        # Keep this block out of the lock! Thread registration will 
        # acquire the lock (thus leading to deadlock, if lock is owned here):
        if numRepeats > 0 or numRepeats == -1:
            musicRepeatThread = RoboComm.MusicReplayDemon(songName, volume, playheadTime, timeReference, numRepeats, repeatPeriod, self);
            self.registerMusicRepeaterThread(musicRepeatThread);
            musicRepeatThread.start();
 def setPlayhead(self, playheadTime, timeReference=TimeReference.ABSOLUTE):
     '''
     Change the music playhead position to a particular time within a song.
     Time may be specified absolute (i.e. relative to the start of the song), 
     or relative to the current playhead. The playhead may be changed during
     playback, or while a song is paused.
     @param playheadTime: New time where to position the playhead.
     @type playheadTime: float
     @param timeReference: TimeReference.ABSOLUTE or TimeReference.RELATIVE
     @type timeReference: TimeReference.
     @raise TypeError: if any parameters are of incorrect type. 
     '''
     if not SpeakeasyUtils.ensureType(playheadTime, int) and not SpeakeasyUtils.ensureType(playheadTime, float): 
         raise TypeError("Playhead must be an int or float indicating seconds. Was " + str(playheadTime));
     
     musicReqMsg = SpeakEasyMusic();
     musicReqMsg.command = MusicCommands.SET_PLAYHEAD;
     musicReqMsg.time = playheadTime;
     musicReqMsg.timeReference = timeReference;
     with self.musicLock:
         self.rosMusicRequestor.publish(musicReqMsg);