The algorithm, in full.
Harmonica fills a queue one position at a time. At each position every song in the library receives a score, and the next song is drawn at random with probability proportional to that score. Nothing is ever removed from the pool. Everything the player knows about your listening enters as an explicit multiplier on that score, and this page lists every one of them, as implemented.
The base score
Every song belongs to one or more groups: an artist, a topic, a franchise, anything worth resting as a unit. Membership carries a share qs,g, split equally by default, so a song is not favoured merely for carrying many labels. A group g holding Ng songs contributes
where mg is the group's rating multiplier, Cg its cooldown, and β a global constant (default 1.25).
The division by Ng makes each group compete as a unit rather than by headcount, and the logarithmic term hands a larger group a measured advantage rather than a proportional one. Double a group's size and its total draw grows by a constant increment, not by a factor. A song's base score is the sum over its groups, and its final score is that base under a chain of multipliers, each of which defaults to exactly 1 and moves only on evidence.
Two further multipliers sit in the chain at 1 unless you use them: a manual per-song weight, and a video-priority boost for when the player is visible on screen.
Cooldowns
A cooldown is a price on recency. After a play it drops to a floor and recovers linearly over a horizon of H subsequent plays.
where d is the number of songs played since the last hearing. A song never played at all sits at 1.
It runs at three levels. The song itself has a floor of zero and a horizon of the whole library, so an immediate repeat is impossible and full recovery takes a library's worth of listening. The song's groups have a floor of 0.05 and a horizon of at most 12, so an artist rests for a few songs but is never shut out. The sub-group, meaning renditions of the same underlying song such as covers, has a floor of 0.01 and a horizon of up to 30, because two versions of one song in close succession is the most conspicuous repetition of all.
Listening is not binary, so neither are cooldowns. Each play carries a repeat credit r between 0 and 1 (defined below), and a partial listen only partially resets the clock, in two ways: the observed distance is stretched to d / r, and the cooldown is blended toward neutral as C′ = 1 − r (1 − C). A song you abandoned in the first seconds barely counts as played at all.
What one listen teaches it
Every play ends at some fraction f of the song's duration, and that position is read as two smooth signals rather than a verdict.
r is the repeat credit above, p the skip penalty. A completion is r = 1, p = 0.
Bailing out inside the first tenth is a full-strength dislike. Skipping past the halfway mark costs nothing, since you gave the song a fair hearing and simply moved on. The penalties are aggregated as a recency-weighted mean with a half-life of 30 events, in which completions count as zeros, so one bad first impression depreciates and later full listens forgive it. The result scales a multiplier
Ratings
Stars map to a multiplier through a piecewise linear curve around a neutral midpoint of 2.5.
So five stars doubles a song's weight and the bottom of the scale halves it, with both ends adjustable in Settings. An unrated song sits at exactly 1, since the absence of a rating is not evidence against a song. Groups receive the same treatment from the mean of their members' ratings, deliberately compressed into a narrower band (0.7 to 1.4) so taste acts mostly at the song level.
Cold start
The aim is that everything gets heard before anything is worn in. Cold start stays active until more than half the library has been played twice. While it is active the draw is restricted in two phases: first to songs never played and never rated, until everything has had a first hearing, and then to songs with fewer than two plays. On top of the restriction, unrated songs carry a boost (default ×2) so first hearings arrive sooner and can be rated.
Satiation
Repetition depreciates a song faster than anything else, so recent listening is metered directly. Each play adds its repeat credit to a running weight that decays with a two-week half-life
A binge therefore suppresses its own object, smoothly and temporarily. The suppression deepens with each recent play, recovers on its own as those plays age out, and is floored so a favourite is rested, never exiled. The strength and window are settings.
Rediscovery
The mirror image. A song rated above the library's mean that has gone quiet accrues a bonus the longer it stays dormant.
The bonus grows with a 60-day half-life, collapses back to 1 the moment the song plays, and never fires for songs below the mean or never played, since unheard songs are cold start's business. Neglected favourites recover their value instead of being forgotten.
Clustering
Some listeners want an artist's songs spread apart, others enjoy a run of them. A bias b between −1 and +1 (default 0) tilts the group cooldown by proximity ρ = 1 − d / H. A positive bias adds bρ to the cooldown so a just-played group returns sooner, and a negative bias multiplies it by (1 + 0.5 bρ), pushing the group's songs further apart.
The draw, and why it never deadlocks
Selection is a weighted random draw, proportional to the scores. After each pick the chosen song and its groups reset their distances, so the queue's own contents cool each other as it fills. If every eligible score reaches zero, which small libraries and aggressive settings can manage, the cooldowns are relaxed in order of importance, group and sub-group first, then the song cooldown, until something is playable.
The guarantee is total: the queue always fills and nothing is ever banned. Over-playing a song merely raises the relative price of hearing it again, and the rest of the library gets correspondingly cheaper.
See it run
The in-browser demo executes these exact source files through Python in your browser, with the settings above exposed as controls. The full specification, including the design arguments and test scenarios, and the implementation (algorithm.py, history.py, ratings.py) are in the repository.