For me, random means: "I want to hear every song in this selection exactly once, chosen randomly". Random should mean uniformly distributed and I don't care whether the same artist or album is played back-to-back. I feel like this is exactly what "shuffle" has meant for decades before some UX quack at Spotify headquarters decided they knew better.
But this usually leads to people perceiving the feature as not being random enough.
See Spotify for example: https://www.quora.com/Is-Spotifys-shuffle-feature-truly-rand...
The hardware restrictions of the earliest IDE HD based MP3 players really have nothing to do with any limitations on modern shuffle ability.
IPod in 2001 had 32MB of RAM. 2k RAM is not relevant to this discussion. You getting close to discussing 3 decade old technology.
Using random number generators for shuffle mode was a stopgap measure that everyone else cargo culted because it's easier than critical thought.
So how about using a maximal linear feedback shift register of smallest size greater than the number of songs, skipping entries that are beyond the end of the list?
Either keep the polynomials[1] in a small list, or implement them as separate functions, invoking the one you need via a function pointer. Both approaches take just a wee bit of extra FLASH/ROM, and just a few bytes of RAM.
https://en.wikipedia.org/wiki/Linear-feedback_shift_register...
For example for a 1000 songs playlist, you choose n to be 2416648531, start with i = 0, you then get the following permutation: 0, 531, 62, 593, 124, 655, 186, 717, 248.
Easiest solution that gets you 99% there would be to make a shuffled list, run through the whole thing, then shuffle again. The only reason why this wasn't done in the past was lack of RAM, which isn't an issue anymore.
Or if RAM really actually is an issue on your embedded device, use a Debruijn sequence.