I'm experimenting with code which will be part of a music player project. However my questions are not about its main purpose (pseudo randomising) but these:
Q1: Is my if() statement the simplest way to pad for 2-digit numbers?
Q2: The current code avoids the same track playing immediately after the previous one. What would be the best approach to avoid repetition within a certain set time? In the eventual sketch I'd probably set this to say a tolerable 20 minutes or so. Scaled for discussion on the code here: assuming one number printed every 100 ms, how to avoid an identical one within say 2 s? I'm reasonably familiar with millis() by now, but so far haven't managed it.
// Prints 5 sets of 15 pseudo random numbers in range: 1-20
int randNumber;
int previous;
void setup()
{
Serial.begin(115200);
delay(200);
Serial.println("RandomSeedA0Test_NoImmedRepeat-3");
randomSeed(analogRead(0));
Serial.println();
Serial.println(F("NEW SESSION"));
for (int k = 1; k <= 5; k++)
{
for (int j = 1; j <= 15; j++)
// set of up to 15 on one line, but ignoring adjacent repeats
{
// Print the numbers on one line.
randNumber = random(1, 21); // Chooses 1 to 20
if (randNumber != previous)
{
previous = randNumber; // Update previous
// Pad so that always prints 2 digit numbers, to line up neatly
if (randNumber < 10) Serial.print("0");
Serial.print(randNumber); Serial.print(" ");
}
} // End of set
Serial.println(); // Start new line for new session
delay(1000); // Wait a bit before printing another set
}
} // End setup
void loop()
{
}
/* Two typical results
--------------------
Three of the five sets in each run had at least one adjacent repetition so was deliberately not printed.
RandomSeedA0Test_NoImmedRepeat-3
NEW SESSION
11 03 01 13 14 12 20 17 07 12 13 12 09
18 15 10 12 17 03 19 15 06 03 05 16 01 20 10
09 10 05 10 01 11 08 01 16 18 04 14 13 18
06 18 20 10 20 06 13 09 03 09 14 15 08 06 08
20 12 07 09 10 17 01 13 19 06 15 06 02 18
RandomSeedA0Test_NoImmedRepeat-3
NEW SESSION
07 08 03 09 06 14 12 19 09 08 11 05 06 18 05
14 09 02 11 06 09 12 06 14 01 08 10 20 08
16 07 06 19 14 18 11 12 06 14 02 04 03
19 07 19 08 12 16 19 01 19 07 19 20 17 05
07 12 04 07 02 13 03 10 08 03 02 12 10
*/
In general when people put on a random shuffle for music they expect it to shuffle like a deck of cards, ie no repeats at all and every track played. If you just don’t repeat in a set time then you might never play a specific track. I would use your pseudo-random generator to organise the tracks and then use your 20 min exclusion for when circling back round so you don’t have repeats in a noticeable period.
Thanks a7. Shortly after posting (and another coffee), I thought I'd cracked it. But I hadn't included the crucial indexed array of your suggestion!
I'll have another go at it, although I anticipate harder than I'd hoped. My music tracks vary widely in duration length (say 2 to 20 mins) . And the array would presumably have to be large to accommodate unlikely but possible very long gaps between repetitions. Haven't done much with arrays so should be a good learning exercise.
Yes, that's definitely its major attraction. As well as being easier, I hope.
Also, as implied by my screenshot, the listener (mainly me) is very likely to navigate elsewhere reasonably soon - an hour at most? - after starting a folder averaging about 110 tracks.
If I had "a lot" of tracks and want to avoid to repeat a played track for ... a certain time, I would just store the played tracks in an array / ringbuffer. If a new random track is needed, compare if you find the track in the existing buffer.
/*
Avoid repeating a random number
https://forum.arduino.cc/t/avoid-repeating-a-random-number-within-a-set-time/1033379/
items needs to be a lot larger than the size of the used buffer!
*/
const uint8_t items = 42; // items on list
const uint8_t size = 10; // size of ring buffer for already used items
uint8_t used[size]; // stores already used colors
byte index; // ring buffer index
constexpr uint8_t btn {2};
void initUsed()
{
for (byte i = 0; i < size; i++)
{
used[i] = random(0, items); // scramble the content of the array (to allow 0 in the first n trys also)
}
}
bool isUsed(byte actual)
{
bool result = false;
for (byte i = 0; i < size; i++)
{
if (actual == used[i]) return true;
}
return false;
}
int getRandomNumber()
{
bool found = false;
int rnd;
while (found == false)
{
rnd = random(0, items);
if (!isUsed(rnd))
{
used[index] = rnd;
index++;
if (index >= size) index = 0;
found = true;
}
}
return rnd;
}
void action()
{
byte actual = getRandomNumber();
Serial.print("index: "); Serial.print(index);
Serial.print("\tactual: "); Serial.print(actual);
Serial.println();
delay(100);
}
void setup() {
Serial.begin(115200);
Serial.println(F("Random with some uniqueness for the last minutes"));
randomSeed(analogRead(A4));
pinMode(btn, INPUT_PULLUP);
initUsed();
}
void loop() {
if (digitalRead(btn) == LOW) action();
}
if you really want the check "against playing time", use a structure and add a millis() timestamp to each entry in your buffer.
I successfully ran it but now need to study it thoroughly to understand how it works. (I'm an 'Arduino programmer', a relatively novice one, and no C/C++ training.) I suspect it will then save me a lot of work.
I've decided against a 'time-based' approach for the reasons mentioned. But another factor (for anyone familiar with the DFR Mini MP3 Player module) is that I'm not yet getting consistent capturing of the currently playing file name (track), which seems an essential requirement.
Edit: Forgot to ask. What exactly do you mean by "Random with some uniqueness for the last minutes" please?
The random seeding you are using may result in the exact same sequence of random numbers every time the thing is powered on. To avoid that, you could use EEPROM for the random seed:
EDIT: And for the second question; If each song has an average duration of 3 minutes and you do not want to repeat anything which has played within the last 20 minutes, you only need to store the 7 most recent tracks:
const uint8_t TRACK_ARRAY_SIZE = 7;
uint8_t track_array_pos = 0;
uint8_t track_array[TRACK_ARRAY_SIZE] = {0};
bool can_play(uint8_t track_no)
{
for (uint8_t i = 0; i < TRACK_ARRAY_SIZE; i++)
if (track_array[i] == track_no) return false;
track_array[track_array_pos] = track_no;
if (++track_array_pos >= TRACK_ARRAY_SIZE) track_array_pos = 0;
return true;
}
Argument "track_no" may not be "0" for this to work.
Thanks, yes, I have been using that EEPROM-based method following help from you and @anon57585045 in July. For the exercise under discussion I kept it simple. And actually, those 30 results look satisfactorily ‘random’ to me. Do you agree?
BTW, with this analog-based seeding method, intuitively I thought an unconnected wire would improve it, but in practice I see no apparent difference.