简体   繁体   中英

Random Interval Generator For a Set Number of Instances Over a Set Period of Time

I was presented with an intriguing problem in c# that I am not sure how to do.

I need to have two tracks playing on top one another. A constant number of beeps need to be played over a set amount of time. One of them will have a set interval( think metronome), but the other needs to be played at random intervals.

I am not sure how to solve that second issue, a set number of beeps played at random intervals over a set amount of time.

Just take that set amount of time, T. Represent it as some sufficiently-fine-grained structure, say milliseconds. If you need to make N beeps, you need to split the timespan N times. So make a loop that runs N times and in each iteration, selects a random location in the time interval for a beep. Depending on what you are doing with the data after that, you may need to sort the beep points.

Use random number generate to generate a datetime within the the range of the total amount of time. When you are done placing the random beeps then the intervals will of course be random. Something like this:

    List<DateTime> randomBeeps = new List<DateTime>();

    Random rand = new Random();
    for( int j = 0; j < numberBeepsNeeded; j++ )
    {
         int randInt = rand.Next();
         double percent = ((double)randInt) / Int32.MaxValue;
         double randTicksOfTotal = ((double)setAmountOfTime.Ticks) * percent;
         DateTime randomBeep = new DateTime((long)randTicksOfTotal);
         randomBeeps.Add(randomBeep);
    }

You might need to use a Convert.ToLong or something of that sort. Not sure if it will give you an error on converting from double to long as it is rounding it, which is fine here.

You can implement this as a series of one-shot timers. As each timer expires (or "ticks"), you play the beep and then randomly determine the duration to use for the next one-shot timer. If the duration you choose is some random number between 1 and 1000 (milliseconds), you will average one "tick" every half-second.

Edit: just for fun, I thought I'd mention that this is an old problem for behavioral psychologists running the kinds of experiments inspired by BF Skinner. They sometimes use a schedule of reinforcement called "Variable Interval", in which the time between reinforcements varies randomly around some predetermined average interval. See http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1404199/pdf/jeabehav00190-0145.pdf for an egg-headed discussion of the formulae involved.

Something like this ought to do the trick (this code not tested...but it compiles clean)

using System;
using System.Security.Cryptography;
using System.Threading;

class BeatBox : IDisposable
{
    private RandomNumberGenerator RNG;

    private DateTime dtStarted;
    private TimeSpan TimeElapsed { get { return DateTime.Now - dtStarted; } }

    private TimeSpan  Duration;
    private TimeSpan  BeatInterval;
    private uint      MinRandomInterval;
    private uint      MaxRandomInterval;
    private uint      RandomIntervalDomain;

    private Timer     RegularIntervalTimer;
    private Timer     RandomIntervalTimer;

    public delegate void TickHandler( object sender , bool isRandom );
    public event TickHandler TickEvent;

    private EventWaitHandle CompletionEventWaitHandle;

    public BeatBox( TimeSpan duration , TimeSpan beatInterval , uint minRandomInterval , uint maxRandomInterval )
    {
        this.RNG = RandomNumberGenerator.Create();

        this.Duration             = duration          ;
        this.BeatInterval         = beatInterval      ;
        this.MinRandomInterval    = minRandomInterval ;
        this.MaxRandomInterval    = maxRandomInterval ;
        this.RandomIntervalDomain = ( maxRandomInterval - minRandomInterval ) + 1 ;
        this.dtStarted            = DateTime.MinValue ;

        this.RegularIntervalTimer = null ;
        this.RandomIntervalTimer  = null ;

        return;
    }

    private long NextRandomInterval()
    {
        byte[] entropy = new byte[sizeof(long)] ;

        RNG.GetBytes( entropy );

        long randomValue    = BitConverter.ToInt64( entropy , 0 ) & long.MaxValue; // ensure that its positive
        long randomoffset   = ( randomValue % this.RandomIntervalDomain );
        long randomInterval = this.MinRandomInterval + randomoffset;

        return randomInterval;
    }

    public EventWaitHandle Start()
    {
        long randomInterval = NextRandomInterval();

        this.CompletionEventWaitHandle = new ManualResetEvent( false );
        this.RegularIntervalTimer = new Timer( RegularBeat , null , BeatInterval , BeatInterval );
        this.RandomIntervalTimer = new Timer( RandomBeat , null , randomInterval , Timeout.Infinite );

        return this.CompletionEventWaitHandle;
    }

    private void RegularBeat( object timer )
    {
        if ( this.TimeElapsed >= this.Duration )
        {
            MarkComplete();
        }
        else
        {
            this.TickEvent.Invoke( this , false );
        }
        return;
    }
    private void RandomBeat( object timer )
    {
        if ( this.TimeElapsed >= this.Duration )
        {
            MarkComplete();
        }
        else
        {
            this.TickEvent.Invoke( this , true );

            long nextInterval = NextRandomInterval();
            this.RandomIntervalTimer.Change( nextInterval , Timeout.Infinite );

        }
        return;
    }

    private void MarkComplete()
    {
        lock ( this.CompletionEventWaitHandle )
        {
            bool signaled = this.CompletionEventWaitHandle.WaitOne( 0 );
            if ( !signaled )
            {
                this.RegularIntervalTimer.Change( Timeout.Infinite , Timeout.Infinite );
                this.RandomIntervalTimer.Change( Timeout.Infinite , Timeout.Infinite );
                this.CompletionEventWaitHandle.Set();
            }
        }
        return;
    }

    public void Dispose()
    {
        if ( RegularIntervalTimer != null )
        {
            WaitHandle handle = new ManualResetEvent( false );
            RegularIntervalTimer.Dispose( handle );
            handle.WaitOne();
        }
        if ( RandomIntervalTimer != null )
        {
            WaitHandle handle = new ManualResetEvent( false );
            RegularIntervalTimer.Dispose( handle );
            handle.WaitOne();
        }
        return;
    }
}

class Program
{
    static void Main( string[] args )
    {
        TimeSpan duration          = new TimeSpan( 0 , 5 , 0 ); // run for 5 minutes total
        TimeSpan beatInterval      = new TimeSpan( 0 , 0 , 1 ); // regular beats every 1 second
        uint     minRandomInterval = 5; // minimum random interval is 5ms
        uint     maxRandomInterval = 30; // maximum random interval is 30ms

        using ( BeatBox beatBox = new BeatBox( duration , beatInterval , minRandomInterval , maxRandomInterval ) )
        {
            beatBox.TickEvent += TickHandler;

            EventWaitHandle completionHandle = beatBox.Start();

            completionHandle.WaitOne();

        }
        return;
    }

    static void TickHandler( object sender , bool isRandom )
    {
        Console.WriteLine( isRandom ? "Random Beep!" : "Beep!" );
        return;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM