Pick a number from 1 to 16
TC
Posts: 1,019
in Propeller 1
Hello all,
I am working on a Halloween costume that is to play fart sounds (little childish i know, but who cares..LOL)
I want it to play up to 16 different sounds with the push of a button, but I want what sound file it plays to be random and not repeating. It would be like a lotto, when it plays a sound file, it flags it as played, and it will not play it again. But when all the sound files have been played, it resets the flags for another round.
I gave it a shot, and I for the life of me I cant figure out why it is not working the way it should. I think it is getting stuck looking for a new random number. Does anyone have any ideas on what I can do?
Thanks
TC
I am working on a Halloween costume that is to play fart sounds (little childish i know, but who cares..LOL)
I want it to play up to 16 different sounds with the push of a button, but I want what sound file it plays to be random and not repeating. It would be like a lotto, when it plays a sound file, it flags it as played, and it will not play it again. But when all the sound files have been played, it resets the flags for another round.
I gave it a shot, and I for the life of me I cant figure out why it is not working the way it should. I think it is getting stuck looking for a new random number. Does anyone have any ideas on what I can do?
Thanks
TC
CON
_clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz
_xinfreq = 5_000_000
CON
''///////////////////////////////////////////////////////////////////
''///
''/// Pins
''///
''///////////////////////////////////////////////////////////////////
sd_CS = 16
sd_DI = 17
sd_CLK = 18
sd_DO = 19
audio_L = 20
audio_R = 21
button = 23
VAR
long track_flag[16]
long track_count
OBJ
wav : "V2-WAV_DACEngine"
ran : "RealRandom"
PUB start | i
wav.begin(audio_L, audio_R, sd_DO, sd_CLK, sd_DI, sd_CS, -1, -1)
ran.start
repeat i from 1 to 16
track_flag[i] := false
main
PUB main
repeat 20
random
repeat
PUB track(track_number) | i
case track_number
1: wav.play(string("01.wav"))
2: wav.play(string("02.wav"))
3: wav.play(string("03.wav"))
4: wav.play(string("04.wav"))
5: wav.play(string("05.wav"))
6: wav.play(string("06.wav"))
7: wav.play(string("07.wav"))
8: wav.play(string("08.wav"))
9: wav.play(string("09.wav"))
10: wav.play(string("10.wav"))
11: wav.play(string("11.wav"))
12: wav.play(string("12.wav"))
13: random
14: random
15: random
16: random
repeat while wav.getBytePosition < wav.getByteSize
track_flag[track_number] := true
track_count := track_count + 1
if track_count == 16
repeat i from 1 to 16
track_flag[i] := false
PUB random | temp
temp := (ran.random / $FFF_FFFF) + 1
if track_flag[temp] == true
random
else
track(temp)

Comments
Also, track calls random again on invalid indices, which just fill up the stack more. You shouldn't do that either. It will eventually crash.
It looks like you want to play each track once in a random permutation. I guess this is so it doesn't play the same thing twice in a row. However, your code doesn't prevent that - it could end a cycle of 16 with one sound, and then start over again with the same sound.
You should look up the Knuth shuffle. Then, to prevent it from playing the same sound twice in a row at the end and beginning of two consecutive cycles, you can make sure they aren't the same and then swap the first sound with a random other sound to fix it. Or, you could keep track of the last 4 sounds played, and keep rejecting random numbers until the number isn't any of those last 4.
You should use "ran.random & $F" instead of "ran.random / $FFF_FFFF". Your code will only ever return -8 to 7, since ran.random is negative half of the time. These negative indices are probably the most serious problem here - it might sort of work if you just fix that.
You'll probably be happier indexing your sounds from 0-15 instead of from 1-16. Computers count from 0, and the sooner you switch to 0-indexing, the sooner you'll stop needing to worry about that pesky +1 everywhere. Also, your track_flag array has 16 elements, and yet you write to element 16, which doesn't exist, clobbering track_count instead.
I was calling random on failure only because I was thinking " If the value was already used, try again" I know it is not the best idea, but I did hope it would work.
Sorry, I'm not fully understanding what you are talking about.
That is exactly correct. What fun would it be if I knew what sound was going to be played. Plus, the repetition would be mind numbing.
I will check out the Knuth shuffle, and try the correction.
I seen this problem a little after I posted the question.
You'll need a couple global variables: one called last, another called played. With these you can make sure all files have played before any repeats, and that you don't play the same file back-to-back.
Then use this method to get a file number from 0..15
pub get_random | lotto repeat lotto := prng.random & %1111 ' 0..15 ifnot (lotto == last) ' if not back-to-back ifnot (played & |<lotto) ' and not already played played |= |<lotto ' add to play list if (played == $FFFF) ' if list is full played := 0 ' reset it last := lotto ' save for next cycle return lotto ' return file to playI use Heater's PRNG object, but you can use ?cnt if you want; the key is to mask the random value with %1111 to create a 0..15 range. Since you're using a button press, the cnt register could be anywhere at the time of the press.
One question...
In the method above, how do you change the number of files? I am familiar with the code in PBasic that uses modulus.
I have tried changing this line to reduce the range, from:
to one less, such as:
But then I get a lockup if the random number is 15 (apparently, I can't directly verify it). So I tried changing this line to match, from:
to:
But that doesn't fix the lockup. Something is out of range, but how do I fix it?
Remember the numbering begins at 0, so the 15 you are looking for is actually 15-1, which is encoded into a 16-bit word, Try
No I haven't tested it.
I use a button to trigger the method, and added PST to show what the value of the lotto each time it's run.
When I change the number of files from %1111 to %1110, the method ends up in a loop and never comes out of it. So the check of the "played" values doesn't work, the PRNG keeps retrying to find a new value.
pub get_random | lotto repeat lotto := prng.random & %1110 ' 0..15 pst.NewLine pst.Str(String("lotto = ")) pst.Dec (lotto) ifnot (lotto == last) ' if not back-to-back ifnot (played & |<lotto) ' and not already played played |= |<lotto ' add to play list if (played == $FFFF) ' if list is full played := 0 ' reset it pst.NewLine pst.Str(String("Reset")) last := lotto ' save for next cycle return lotto ' return file to playThere's a part of the technique here I don't understand.
lotto := prng.random & %1110
picks even numbers ranging from 0 to 14
You were right the first time: the number should be &'d with %1111 to get all numbers from 0 to 15.
-Phil
This will only give even numbers.
>>> if (played == $FFFF) ' if list is full
This needs to check for $3FFF
Attached is a working program.
John Abshier
If your list is arbitrary length, then use modulus.
lotto := prng.random // 10
Will give you a number from 0 to 9. The 10 can be any number (although numbers less than 1 are likely not what you want).
Using & requires that your list length/number be a power of 2.
Note: you'll have to change the "played == $FFFF" comparison to have the proper number of bits in it to match your list size.
lotto := prng.random // 10
will produce some numbers more often that others.
Not totally sure but I think mod 10 will produce the numbers 0 to 5 about twice as often as the numbers 6 to 7.
For a nice explanation and solution see here: https://zuttobenkyou.wordpress.com/2012/10/18/generating-random-numbers-without-modulo-bias/
Random numbers are slippery things.
-Phil
Still, it's a bias. And easy to remove. If you are fussy like me
Can you elaborate, Roy? If the desired sub-range is 0 to 2**n - 1 and the range of the RNG is 0 to 2**m - 1, where m >= n, there is no bias from anding.
-Phil
However, in any case, I think the bias isn't going to matter in the sample sizes here.
Thanks for the explanation - I've got it now.
For anyone else in the future...the easy way to generate the (played == ) hex value is to go to:
http://www.binaryhexconverter.com/binary-to-hex-converter
Then type in the number of bits you want to track. Hit Convert and there's your answer. There are also lots of apps for your smartphone, but you're already on the computer, so...
And Heater, thanks! One of the reasons I like this forum is how I learn new things. I didn't know about the modulo bias. Really interesting discussion!
I used a variant of that code in my friend's Stranger Things project. He had 200 WS2811 Christmas type bulbs that he wanted to randomly populate. Here's the method that controls populating the initial colors of the string.
pub colorize_string(ms) | list, last, full, ch, idx, mask '' Randomizes the colors of the string '' -- ms is the fade-up timing '' -- attempts to create even distribution of color table list := 0 last := -1 full := -1 >> (32 - COLORS) ' make full mask if (ms < 50) ms := 0 repeat ch from 0 to STRIP_LEN-1 ' colorize all bulbs repeat idx := ||(prng.random // COLORS) ' randomize color if (idx <> last) ' if not a repeat mask := 1 << idx ' create color mask ifnot (list & mask) ' if not already used this cycle list |= mask ' add to list if (list == full) ' if all colors used list := 0 ' reset the list last := idx ' save for next cycle quit ' quit to color the bulb fade(ch, ColorTable[idx], 1, 255, ms)As you can see, I used modulus so that Matt could update the number of colors in the string.