def search_song(query, options = {})
      assert_valid_keys(options, :artist, :duration, :page)
      options = {:page => 1}.merge(options)

      raise(APIError, 'User must be in a room to search for songs') unless room

      if artist = options[:artist]
        query = "title: #{query}"
        query << " artist: #{artist}"
      end
      query << " duration: #{options[:duration]}" if options[:duration]

      api('file.search', :query => query, :page => options[:page])

      conditions = {'query' => query}

      # Time out if the response takes too long
      EventMachine.add_timer(@timeout) do
        trigger(:search_failed, conditions)
      end if @timeout

      # Wait for the async callback
      songs = wait do |&resume|
        on(:search_completed, :once => true, :if => conditions) {|songs| resume.call(songs)}
        on(:search_failed, :once => true, :if => conditions) { resume.call }
      end

      # Clean up any leftover handlers
      @event_handlers[:search_completed].delete_if {|handler| handler.conditions == conditions}
      @event_handlers[:search_failed].delete_if {|handler| handler.conditions == conditions}

      songs || raise(APIError, 'Search failed to complete')
    end
def search_song(query, options = {})
      assert_valid_keys(options, :artist, :duration, :page)
      options = {:page => 1}.merge(options)

      raise(APIError, 'User must be in a room to search for songs') unless room

      if artist = options[:artist]
        query = "title: #{query}"
        query << " artist: #{artist}"
      end
      query << " duration: #{options[:duration]}" if options[:duration]

      api('file.search', :query => query, :page => options[:page])

      conditions = {'query' => query}

      # Time out if the response takes too long
      EventMachine.add_timer(@timeout) do
        trigger(:search_failed, conditions)
      end if @timeout

      # Wait for the async callback
      songs = wait do |&resume|
        on(:search_completed, :once => true, :if => conditions) {|songs| resume.call(songs)}
        on(:search_failed, :once => true, :if => conditions) { resume.call }
      end

      # Clean up any leftover handlers
      @event_handlers[:search_completed].delete_if {|handler| handler.conditions == conditions}
      @event_handlers[:search_failed].delete_if {|handler| handler.conditions == conditions}

      songs || raise(APIError, 'Search failed to complete')
    end
Finds songs that match the given query.

 @note The user must be entered in a room to search for songs
 @param [String] query The query string to search for.  This should just be the title of the song if :artist is specified.
 @param [Hash] options The configuration options for the search
 @option options [String] :artist The name of the artist for the song
 @option options [Fixnum] :duration The length, in minutes, of the song
 @option options [Fixnum] :page The page number to get from the results
 @return [Array<Turntabler::Song>]
 @raise [ArgumentError] if an invalid option is specified
 @raise [Turntabler::Error] if the user is not in a room or the command fails
 @example
   # Less accurate, general query search
   client.search_song('Like a Rolling Stone by Bob Dylan')             # => [#<Turntabler::Song ...>, ...]

   # More accurate, explicit title / artist search
   client.search_song('Like a Rolling Stone', :artist => 'Bob Dylan')  # => [#<Turntabler::Song ...>, ...]