Skip to content

Radio Behavior

Protected edited this page Nov 29, 2023 · 1 revision

A Grabber add-on that will make Rowboat connect to a voice channel and play a music radio using songs listed in the Grabber index. Songs are automatically selected using a complex algorithm.

Radio can integrate with a SongRanking instance for tailoring song selections to listener preferences.

Playback begins when there is at least one non-deafened user connected to the channel and pauses when there are no longer any non-deafened users in the channel. If a user connects or un-deafens themselves during the pause period, playback resumes on the paused song. If the pause period has since expired, playback restarts with a new song.

Setup and parameters

By default, Radio will play in the voice channel specificed in the channel parameter. A new channel can be specified during runtime using !radio on. !radio off can be used to shut down the radio during runtime.

Other parameters include:

  • announcechannel: An optional text channel for announcing when users start or stop listening and when a new song starts playing. Song announcements contain the song hash and are compatible with SongRanking reactions. Channel output can be fine-tuned using the announcesongs, announcejoins and announceskips parameters.
  • usestatus: Use the bot's presence to display the current Radio status including which song is playing.
  • volume and referenceloudness: Control playback volume. Radio uses the referenceloudness parameter along with the Grabber index loudness and tweak metadata of each song to dynamically adjust the volume for a pleasant listening experience.
  • pause: How long to pause a song when there are no listeners. When the pause period expires, playback stops.
  • leadin: Seconds of silence between songs.
  • autowithdraw: If a user ceases to be a listener for longer than this amount of time, their requests will be removed from the queue.

Playback control

Skipping songs

Under normal circumstances, Radio will play a song all the way through before selecting a different song (unless no listeners remain and the pause period expires). However, listeners can also skip the current song:

  • !radio skip signals to Radio that the user doesn't want to listen to the current song. It will deafen the user until the end of the current song and place a vote for skipping the current song. If all listeners vote to skip the current song, a new song will play immediately. Otherwise, the song will keep playing. When playback of the current song ends, all listeners who voted to skip will be undeafened.
  • !radio another will immediately end playback of the current song and play a new song. By default, this command is restricted to moderators.

Request specific songs

It's possible for listeners to request that specific songs are played as long as they are in the Grabber index. If songs are in the request queue, then by default when a song is next selected for playback, it will be selected from the queue (this behavior can be controlled in configuration).

Requests can be made before connecting to the radio channel.

  • !radio request HASH: Adds a song to the end of the request queue.
  • !radio demand HASH: Adds a song to the beginning of the request queue (it will play next). By default, this command is restricted to moderators.
  • !radio withdraw: Withdraw your requests from the request queue.
  • !radio queue: Show the contents of the request queue.

Ignore preferences when selecting songs

By default, if a SongRanking instance has been associated with Radio, your preferences are taken into account each time a song is automatically selected for playback. If you're looking for new music, you can use !radio neutral to signal that you don't want your preferences taken into account until the end of the current session (or until you use the command again).

Scheduled disconnection

If you know you gotta go, but still want to listen to the current song all the way to the end, or if you know you have to leave soon, you can ask Radio to disconnect you automatically after a certain number of songs.

  • !radio end: Disconnects the listener after the current song is over.
  • !radio end N: Disconnects the listener after N more songs, including the current one.
  • !radio end check: Shows how many songs are left before the listener will be disconnected.
  • !radio end cancel: Changed your mind? This command will abort the scheduled disconnection.

The Radio song reference parser

Radio adds a song reference parser that can be used to reference the songs that it has played:

  • $: References the song that's currently playing (even if it's paused).
  • $1, $2, $3, ..., $N: References the song that played N songs ago. Only a limited history is kept, and it isn't persisted when Rowboat shuts down. Use !radio history to see the current logged history.

The song selection algorithm

At the heart of the Radio behavior lies the algorithm used for selecting the next song that will be played. This algorithm has undergone many iterations and can be, at time of writing, tweaked through the adjustment of 27 different parameters in configuration. This section only provides a high level overview of how it works.

When Radio wants to select a new song for playback, it will calculate a heuristic for every song in the Grabber index. This heuristic is referred to as priority, and represents the likelihood of the song being selected for playback. If the priority is 0, the song cannot be selected. The selection is made from all the candidates with a priority greater than 0.

PRIORITY = (BASE + F_RANK + F_LISTENER + F_LENGTH + F_LOWPLAYS) ^ PLAYS_CURVE * RECENT_MITIGATION * PENALTY_UNANIMOUS
  • Rank factor: If SongRanking is present, the Song rank is taken into account.
  • Listener factor: If SongRanking is present, each listeners' preferences (unless they used !radio neutral) is taken into account (see below).
  • Length factor: Take into account whether the song approaches an optimal length range, making overly short and overly long songs play more rarely.
  • Low plays factor: Priority boost songs that have been played very few times.
  • Comparative plays curve: Modify priority on a curve, where the song that has the most plays will not be played. Prevents the same (most liked) songs from playing all the time.
  • Recent mitigation: Reduce priority of songs that played recently (using timestamps, not playback order).
  • Unanimous penalty: If SongRanking is present, greatly reduce priority of songs that everyone (who didn't use !radio neutral) dislikes. By default, songs that everyone hates will always have a priority of 0.

When calculating the listener factor, Radio takes into account three components for each current listener:

  1. Combined rank from Curators.
  2. Attenuation from Keywords.
  3. Attenuation from skips (if you skip a song often, your preference for it has less weight).

Finally, there are two special modes where the default song priority heuristic is replaced entirely.

  • Play from the request queue: If the request queue contains any songs, there is a chance (by default 100%) that songs not in the request queue will have a priority of 0.
  • Play novelties: If there are novelties, there is a chance that songs that are not novelties will have a priority of 0. Novelties are songs learned recently (no more than a certain number of days ago) that haven't yet reached a certain number of plays.

Users can use !radio apriority to analyze the current priority calculation of any song in the index.

Curators and keywords

Users can have fine grained control over how their preferences affect their listening experience by using curators and keywords. Control over these preferences is available using the !rpref command. Radio preferences are persisted even if Rowboat shuts down.

Curators are users whose SongRanking preferences should be taken into account when calculating how you affect the listener factor during song selection (see above). By default, you are your own sole curator, meaning that if you are listening, songs you like will have a higher priority, and songs you dislike will have a lower priority. However, you can select any amount of users to be your curators, meaning you want to listen to songs they like.

You can also set users as reverse curators: This means that while you're listening, songs they dislike will have a higher priority, and songs they like will have a lower priority.

Keywords are words that, if found (or not found) in the song's index metadata, will lower the priority of the song when you are listening.

The !rpref command also lets you create multiple listening profiles with different curators and keywords. Profiles can be swapped quickly for a different listening experience depending on your mood.

The Radio library webpage

NOTE: This feature is pending refactoring.

Radio comes with a non-mobile-friendly webpage that you can deploy in your server for searching the Grabber index and quickly obtaining information on user preferences, statistics, and song playback.

The files can be found in extra/radio and require php. The webpage must be deployed on the same server running Rowboat, since it will access the index and statistics files generated by Rowboat directly (through the filesystem).

To deploy, simply copy this folder to a web-accessible location and edit the variables in config.inc.php.

Browsing the table

The webpage contains a table with several columns. Click a column to sort the table by that column; click it again for reverse order. You can also shift+click multiple columns to sort by several criteria (the selection order is the criterium priority).

The leftmost column displays the format of the song in the cache and the song's hash. Double click a hash to select it for copying.

You can click cells in the Q column to select them. ctrl+click to select multiple columns, and shift+click to select ranges. Use the "Highlight selection" button at the top to generate a highlight filter for your selection; highlighted songs will have a yellow background. Statistics about your selection will be displayed on the top bar.

This column will also show if a song is currently being played, and if it's in the request queue (the number will be the position in the queue). Values only update on refresh/search/sort.

Click the song Name to go to the source of the song. The Author and Album links will take you to DuckDuckGo.

Clicking the contents of the Shared by column will open a box with statistics for that user. The same box can be accessed by clicking the emoji in the Likes column, which contains SongRanking likeabilities. Hover your mouse over an emoji to see whose likeability it is. Sorting by this column will sort index entries by rank.

The KW column can be clicked to see a list of keywords associated with a song in the index.

The LP column shows the priority of a song as of the latest selection.

Songs with a cyan background are novelties (recently added songs with few plays).

The search bar

The bar on the top right corner can be used to filter the library table using criteria of any level of complexity. By default, a word by word search will be performed. For example, bowling soup will return anything that contains those two words, in that order, in any field.

| can be used to combined the results from multiple filters in the same list. Example: bowling soup|protected.

{search string} can be used to perform an exact search by that search string instead of word by word. Example: {wolves of the sea}. {!search string} inverts the search, meaning only non-matching results are returned. When performing an exact search, * can be used as a wildcard.

{fieldname=search string} will search only in a specific field instead of every field. To invert this type of filter, use {fieldname!=search string}. Example: {format!=flac}

[regular expression] and [fieldname=regular expression] will search using a regular expression instead of exact search. Syntax rules are otherwise the same as those for {} patterns.

Multiple patterns can be used in each filter, and the results will be an intersection of every pattern. Example: [Protected:[+-]2]{sharedBy!=Protected} returns everything Protected felt strongly about that was not shared by him.

@ : Prefix your entire search string with @ to merely highlight the results in the full list instead of filtering the list. Example: @{sharedBy=Protected}.

Search field names

The following are the field names that can be used in the filter patterns explained in the previous section.

  • hash
  • format - mp3, flac or pcm
  • sharedBy - If multiple people shared the same song, each will be matched individually. Discord user IDs and display names are both supported.
  • length - In seconds only
  • sourceSpecificId - This is the Youtube ID (matches Youtube sources only)
  • name
  • author
  • album
  • track
  • keywords - This will match against each keyword individually
  • like - This will match against each SongRanking likeability individually, using the format: USER:VOTE, where VOTE is -2, -1, +1 or +2. Discord user IDs and display names are both supported.
  • plays