Corey Ford Corey Ford - 2 months ago 49
C Question

How to solve infinite loop for generated music?

I have a program that I want to create a randomly generated bar of music (4 beats to a bar, using C Major scale). However, I'm having trouble understanding the maths and keep overflowing my do while loop, creating more than 4 notes to the bar which I want to avoid.

I am using aServe which was created by my tutor, but basically opens a stream to an Oscillator that plays the arguments I've commented.

Hope someone can help!!!

/* Program for randomly written bar of 4/4 in C Major */

#include "aservelibs/aservelib.h"
#include <stdio.h>
#include <stdlib.h>

//macros
#define SEMIBREVE (1.0)
#define MINIM (1.0/2)
#define CROTCHET (1.0/4)
#define QUAVER (1.0/8)
#define SEMIQUAVER (1.0/16)
#define DEMISEMIQUAVER (1.0/32)

#define C (261.63)
#define D (293.66)
#define E (329.63)
#define F (349.23)
#define G (391.99)
#define A (440.00)
#define B (493.88)

int millisec(int bpm, double note) {
return (int)(
60 /* seconds */
* 1000 /* milliseconds per second */
* 4 /* crotchets per semibreve */
* note
/ bpm
);
}

int main()
{
int bpm = 120; //BPM Value
double Length[] = {SEMIBREVE, MINIM, CROTCHET, QUAVER, SEMIQUAVER, DEMISEMIQUAVER}; //Array of Note Lengths
double Pitch[] = {C, D, E,F, G, A, B}; //Array of CMajor Scale Freq

int randLength = (rand() % 6); //random positions for note length
int randPitch = ( rand() % 7); //random positions for note pitch
double barTotal = 0; //amount of bar currently completed

do {
if(barTotal < 1) //if bar total is smaller than 1
{
barTotal = Length[randLength] + barTotal; //add note to total

aserveOscillator(0, Pitch[randPitch], 1, 2); //Starts stream to oscialltor
//aserveOscillator(Index,Frequency,Amplitude,WaveType);
aserveSleep(millisec(bpm, Length[randLength])); //play the notes for the length of time specified in milliseconds

randLength = (rand() % 6); //prepare next random note
randPitch = (rand() % 7); //prepare next random pitch

//Output
printf("Note: ");
printf("%lf", Pitch[randPitch]);
printf("\n For: ");
printf("%lf", Length[millisec(bpm,randLength)]);
printf("\n With Bar Total: ");
printf("%lf", barTotal);
printf("\n\n");
}
else
{
if(barTotal != 1) //if bar total is bigger than 4
{
randLength = (rand() % 6); //try another number
}
}

} while (barTotal != 1); //will stop once reaches 4

return 0;
}

Answer

Consider thinking about the problem differently. Think of a bar as "n" slots where n is the most granular note type you have. So in your case a bar is a group of 32 slots. Rather than representing your numbers as fractions, use integral types to show how many of those "slots" each takes. So a DEMISEMIQUAVER takes 1 slot, which can be represented as an int rather than being (1.0 / 32.0) which introduces some potentially ugly issues.

Once you do this the solution is more straightforward:

1) How many slots are left in the current bar? 2) Choose a random note from a pool of notes smaller than the remaining slots 3) Recalculate how much room is left after adding the new note 4) If the remaining room is zero, proceed to the next bar.

Below is your code, adapted to this new approach. Not fully tested but it should avoid most if not all of the pitfalls discussed thus far.

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//macros
#define SEMIBREVE       (32)
#define MINIM           (16)
#define CROTCHET        (8)
#define QUAVER          (4)
#define SEMIQUAVER      (2)
#define DEMISEMIQUAVER  (1)

#define C   (261.63)
#define D   (293.66)
#define E   (329.63)
#define F   (349.23)
#define G   (391.99)
#define A   (440.00)
#define B   (493.88)

int GetMaxIndex(int remainingLength)
{
    // Returns the largest upper bound of the Length array that
    // should be considered based on how much room remains in 
    // the current bar.
    int result;
    if(remainingLength == 32) result = 5;
    if(remainingLength < 32) result = 4;
    if(remainingLength < 16) result = 3;
    if(remainingLength < 8) result = 2;
    if(remainingLength < 4) result = 1;
    if(remainingLength < 2) result = 0;
    return result;

}

int main()
{
    double Pitch[] = {C, D, E,F, G, A, B}; //Array of CMajor Scale Freq

    int bpm = 120; //BPM Value
    int Length[] = {DEMISEMIQUAVER, SEMIQUAVER, QUAVER, CROTCHET, MINIM, SEMIBREVE}; //Array of Note Lengths
    char* Labels[] = {"DEMISEMIQUAVER (Thirty Second)", "SEMIQUAVER (Sixteenth)", "QUAVER (Eighth)", "CROTCHET (Quarter)", "MINIM (Half)", "SEMIBREVE (Whole)"}; 
    int remainingThisBar;
    int barsToGenerate = 4;

    int randLength = (rand() % 6); //random positions for note length
    int randPitch; //random positions for note pitch
    int maxIndex;
    int randIndex;

    srand(time(NULL));

    for(int barNumber = 0; barNumber < barsToGenerate; barNumber++)
    {
        printf("Beginning bar: %i\n", barNumber);
        remainingThisBar = 32;

        while(remainingThisBar > 0)
        {
            maxIndex = GetMaxIndex(remainingThisBar);  // What is the biggest note index we still have room for?
            randIndex = maxIndex == 0 ? 0 : (rand() % maxIndex); // Get a random note between 0 and maxIndex

            randPitch = ( rand() % 7);      // Random positions for note pitch
            randLength = Length[randIndex]; // Length in 32nds
            remainingThisBar -= randLength; 

            // Output
            printf("\tNote: %s @ %f\n", Labels[randIndex], Pitch[randPitch]);
            printf("\t32nds remaining in bar: %i\n", remainingThisBar);
            printf("\n");

            /* TODO - Output note via aServe*/
        }
    }
}
Comments