2

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 math 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.

/* 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;
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Corey Ford
  • 178
  • 1
  • 13
  • There are several comparisons in your code to the value 4 but it appears that a whole note (SEMIBREVE) has a value of 1.0. If 1.0 represents the length of a bar then these comparisons would seem to be incorrect. – dazedandconfused Sep 28 '16 at 15:13
  • 1
    `barTotal` is a `double`, and you're adding fractions to it. The likelihood of it exactly equaling 4 (and thus breaking from the loop) is extremely small I would think. Furthermore, you're comparing it to 4 instead of 4.0, I'm not sure what the implications there are. Why not just have an `int` that keeps track of the number of notes? – yano Sep 28 '16 at 15:13
  • @dazedandconfused I have amend the code I believe to change this. – Corey Ford Sep 28 '16 at 15:15
  • @yano Correct me if I'm wrong, but I want a random number of notes made up of the randomised note lengths to fill a bar of 4/4 - instead of just x number of notes. – Corey Ford Sep 28 '16 at 15:16
  • If I made the while loop terminate when (barTotal < 1), is it possible to calculate notes that will fill the remaining length of the bar? – Corey Ford Sep 28 '16 at 15:20
  • So you just want to add random fractions from the `Length[]` array to `barTotal` until `barTotal` is equal to 1.0? – yano Sep 28 '16 at 15:20
  • @yano precisely, yes. – Corey Ford Sep 28 '16 at 15:21
  • What happens when you run the altered code? Also, @yano's comments about floating point precision should be considered. Lastly, think about what happens if the first note you generate is a half note and the second note randomly chosen is a whole note. Your statement barTotal = Length[randLength] + barTotal; is going to give you a value greater than 1. I think you need to see if the proposed note will exceed the remaining space and, if so, reduce its length until it is acceptable. – dazedandconfused Sep 28 '16 at 15:21
  • I think the loop you have is a little convoluted for that, but the biggest problem I see (I think), is with those random fractions, what's the likelihood of landing exactly on 1.0? Maybe you'll go from 0.93 to 1.04,, then you're still stuck in the loop. Perhaps you should change the condition to exit on `>= 1.0` unless you need it exactly at 1.0 – yano Sep 28 '16 at 15:23
  • You can just replace `if (barTotal < 1)` with `if (barTotal + Length[randLength] <= 1)`. Then in the matching `else`, the additional test `if(barTotal != 1)` is redundant - you can just choose a new `randLength` regardless. (Note: this isn't very efficient, but it should at least work!) – Ian Abbott Sep 28 '16 at 15:25
  • @IanAbbott This has certainly made the final value for bar total = 1.0. However now the random functions always seem to be returning the same values. Weird. – Corey Ford Sep 28 '16 at 15:34
  • 2
    You'll need to seed the random function with time to get different values each time you run it: http://stackoverflow.com/questions/8314370/rand-with-seed-does-not-return-random-if-function-looped – yano Sep 28 '16 at 15:39
  • Other problems in the code: (1) The `l` in `%lf` is redundant. (2) `Length[millisec(bpm,randLength)]` should probably be `millisec(bpm,Length[randLength])`. (3) You should probably print that stuff about the current note _before_ clobbering the `randLength` and `randPitch` variables with new values! – Ian Abbott Sep 28 '16 at 15:49

1 Answers1

1

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*/
        }
    }
}
dazedandconfused
  • 3,131
  • 1
  • 18
  • 29