25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

Random music C++ class

A C++ class (Scale) developed for a program that generates random music. It builds musical scales in 12 keys and in a variety of musical modes. It was written in Borland C++ 4.0 for a MSDOS target, and uses the BIDS container classes (not STL).

New!The latest version of this program is for Microsoft Visual C++ 2005 and STL.

The Scale class and the program that uses it place restrictions on the sequences of notes generated, in an attempt to make the output sound as musical as possible:

  1. Restriction to key and mode. The program selects one of the 12 musical keys; surprisingly, even on a computer, different keys sound different, probably due to the PC speaker's response characteristics. It then selects one of several musical modes (major, minor, etc.), and determines which notes of the 88 available are legal for that key and mode. From that point on, it only plays notes that are legal for that key and mode.
     
  2. Limitation of interval size using a normal distribution with a specified standard deviation. If you merely select random notes from the set of legal ones, the result will sound terrible because a note from the middle of the keyboard followed by a note at the top end followed by a note at the bottom end does not have enough continuity to sound musical. Given a starting note, the next note must usually be close enough to it to provide continuity, but it must also be somewhat unpredictable to avoid being boring.

    Thus, this program selects each next note using a normal distribution centered on the current note, with a standard deviation that the user specifies. For example, using a starting note of Middle C, a C Major scale, and a standard deviation of 4, the next note in the sequence is most likely to be Middle C again (implemented as a double-duration note), slightly less likely to be 1 legal note higher or lower than Middle C (B or D), even less likely to be 2 notes higher or lower (A or E), and so on. About 2/3 of the time, the next note will be within the 4-notes-lower to 4-notes-higher range, but occasionally larger intervals might be used. Thus, most of the time the music is continuous, but there are occasional larger intervals to create variety.

These strategies seemed to work quite well, and there were times when the program created short bursts of melody that I wished could be captured and further developed manually. The program was working as a musical motive and theme generator! That's why the option was added to save the note sequence to a file. I never wound up using it that way, but I think the potential is definitely there.

Interesting enhancements would be:

  1. Give it direct MIDI output routines to get it away from the PC speaker.
  2. Give it the ability to generate multiple simultaneous notes, polyphony.
  3. Allow selecting which MIDI instruments to use.

music.h

/*	music.h			2-25-03
	Copyright (C)1995, 2003 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

	This file should contain any classes and methods useful for music applications,
	and should contain all the functionality possible that isn't platform
	dependent: i.e. that is useful for both DOS sound() and Win MIDI.

*/
#ifndef __MUSIC_H
#define __MUSIC_H

#include <math.h>
#include <classlib\arrays.h>
#include "c:\bcs\my.h"
#pragma hdrstop

//////////////////////////////////////////////////////////////////////////////
// globals
//----------------------------------------------------------------------------
// the various modes.  There is a matching list of names in .CPP: ensure they always match.
//       0         1      2      3		  4			  5		    6	    7	     8
enum {CHROMATIC, MAJOR, MINOR, BLUES, PENTATONIC, WHOLETONE, MMINOR, HMINOR, MIXOLYDIAN };

//////////////////////////////////////////////////////////////////////////////
//
class Scale
{
public:
	Scale();
	virtual ~Scale();

	friend ostream& operator << (ostream& os, const Scale& s);	// write
	friend istream& operator >> (istream& is, Scale& s);		// read

	// functions
	BOOL IsLegal(int note);		// whether the given note is legal for the current key and mode
	int TranslateToNote(double x, double bottom, double top); 		// maps value into 0-87
	int TranslateToScaleNote(double x, double bottom, double top);	// maps value into NoteArray
	uint TranslateToSound(double x, double bottom, double top);		// maps value into a linear scale
	void Randomize();         	// randomize all the random note generation variables
	int GetNextRandomNote();	// returns next random note in the current sequence
	string GetRandomInfo();		// text info about the random set
	int GetFreq(int note);		// the frequency corresponding to the given note number
	int CurrentFreq();			// frequency of the note just randomly generated.
	int FillNoteArray();		// fill the array with notes

	// variables
	int CurrentKey;		// the key now in use; A = 0...
	int CurrentMode;	// the mode now in effect

						// THESE VARIABLES USED FOR RANDOMIZING KEYS AND NOTES
	double StdDev;		// standard deviation of distribution next random note is chosen from
						// 4 seems best, has wide variety.  3 is also good.
						// 2 is too stable.  5 is too wild.
	int NoteLength;		// the time value in milliseconds of the shortest note you use.
	int CurrentNote;	// the note just randomly generated.
	int PrevNote;		// the note just previous.

	string Major;		// lists of notes legal for the named mode, as chars; root = 0.
	string Minor;
	string Blues;
	string MMinorUp;
	string HMinor;
	string Pentatonic;
	string WholeNote;
	string Mixolydian;

	TArrayAsVector<long> NoteArray; 	// currently holds a major scale of notes

protected:

};
//----------------------------------------------------------------------------
// 							end class Scale
//////////////////////////////////////////////////////////////////////////////
#endif			// __MUSIC_H

music.cpp

/*	music.cpp		2-25-03
	Copyright (C)1995, 2003 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

*/
#include "c:\bcs\library\music.h"

//////////////////////////////////////////////////////////////////////////////
// Globals
//----------------------------------------------------------------------------
// make sure these always match the enums in music.h!
char* ModeNames[] =
{"CHROMATIC","MAJOR","MINOR","BLUES","PENTATONIC","WHOLETONE","MMINOR","HMINOR","MIXOLYDIAN"};

//////////////////////////////////////////////////////////////////////////////
// class Scale code
//----------------------------------------------------------------------------
// constructor
Scale::Scale() : NoteArray(88, 0, 1)
{
	CurrentKey = 3;			// default is C Major
	CurrentMode = MAJOR;
	PrevNote = 0;			// a definite starting value, but arbitrarily chosen
	CurrentNote = 0;
	StdDev = 4.;
	NoteLength = 200;		// ms

	int major[] = 	 	{0, 2, 4, 5, 7, 9, 11, 255};
	int minor[] = 	 	{0, 2, 3, 5, 7, 8, 10, 255};
	int blues[] = 	 	{0, 3, 5, 7, 10, 255};
	int mminorup[] = 	{0, 2, 3, 5, 7, 9, 11, 255}; 	// mminordown is same as normal minor
	int hminor[] = 	 	{0, 2, 3, 5, 7, 8, 11, 255};
	int penta[] = 	 	{0, 2, 4, 7, 9, 255};
	int whole[] = 	 	{0, 2, 4, 6, 8, 10, 255};
	int mixolydian[] =	{0, 2, 4, 5, 7, 9, 10, 255};

	int i;
	for(i = 0 ; major[i] != 255			; i++) Major 		+= (char)(major[i]);
	for(i = 0 ; minor[i] != 255 		; i++) Minor 		+= (char)(minor[i]);
	for(i = 0 ; blues[i] != 255 		; i++) Blues 		+= (char)(blues[i]);
	for(i = 0 ; mminorup[i] != 255		; i++) MMinorUp 	+= (char)(mminorup[i]);
	for(i = 0 ; hminor[i] != 255 		; i++) HMinor 		+= (char)(hminor[i]);
	for(i = 0 ; penta[i] != 255 		; i++) Pentatonic 	+= (char)(penta[i]);
	for(i = 0 ; whole[i] != 255 		; i++) WholeNote 	+= (char)(whole[i]);
	for(i = 0 ; mixolydian[i] != 255 	; i++) Mixolydian	+= (char)(mixolydian[i]);
	// it might be interesting to add the other Greek modes

	FillNoteArray();			// fill array with notes
}                       		//constructor
//----------------------------------------------------------------------------
// destructor
Scale::~Scale()
{
}                       	//destructor
//----------------------------------------------------------------------------
// fill NoteArray with all the legal notes in the current key and mode
// set the key and mode you want before calling.
int Scale::FillNoteArray()
{
	NoteArray.Flush();
	for(int i = 0 ; i < 88 ; i++)
		if(IsLegal(i))
			NoteArray.Add((long)i);
	return NoteArray.GetItemsInContainer();
}                      		//FillNoteArray
//----------------------------------------------------------------------------
// the frequency corresponding to the given note number, rounded to the nearest whole number.
int Scale::GetFreq(int note) { return round(27.5 * pow(ROOT12OF2, note)); }
//----------------------------------------------------------------------------
// frequency of the note just randomly generated, rounded to the nearest whole number.
int Scale::CurrentFreq() { return GetFreq(CurrentNote); }
//----------------------------------------------------------------------------
// text info about the random set
string Scale::GetRandomInfo()
{
	ostrstream os;
	os	<< "Key = " << CurrentKey  << ",  Mode = " << CurrentMode
		<< " (" << ModeNames[CurrentMode] << "),  StdDev = "
		<< StdDev << ends;
	string s = os.str();
	delete[] os.str();
	return s;
}         					//GetRandomInfo
//----------------------------------------------------------------------------
void Scale::Randomize()
{
	// use whatever values prove interesting
	NoteLength = 40 * random(11);
	CurrentKey = random(12);
	CurrentMode = random(9);
	StdDev = random(5) + 1;		// 1 to 5
}							//Randomize
//----------------------------------------------------------------------------
// whether the given note is legal for the current key and mode
BOOL Scale::IsLegal(int note)
{
	BOOL legal = FALSE;
	if((note >= 0) && (note <= 87))
	{
		// note % 12 normalizes it down to the base octave.
		// note - CurrentKey normalizes it down to the base scale of A (note 0).
		// so if it is legal NOW in A, then the real note is also legal in its key.
		int i = (note % 12) - CurrentKey;
		if(i < 0)				// but the normalizing can take it to the octave below
			i += 12;			// so bring it back up.
		char base = (char)i; 	// separate assignment here because chars are unsigned.
		switch(CurrentMode)
		{
			case MAJOR: 		legal = Major.contains(base); break;
			case MINOR: 		legal = Minor.contains(base); break;
			case BLUES: 		legal = Blues.contains(base); break;
			case PENTATONIC:	legal = Pentatonic.contains(base); break;
			case WHOLETONE: 	legal = WholeNote.contains(base); break;
			case HMINOR:		legal = HMinor.contains(base); break;
			case MIXOLYDIAN:	legal = Mixolydian.contains(base); break;
			case MMINOR:
				if(note > PrevNote)
					legal = MMinorUp.contains(base);
				else
					if(note < PrevNote)
						legal = Minor.contains(base);
					else
						legal = MMinorUp.contains(base) || Minor.contains(base);
				break;
			case CHROMATIC:
			default:
				legal = TRUE;	// true to prevent accidental hangups
				break;
		}
	}
	return(legal);
}								//IsLegal
//----------------------------------------------------------------------------
// returns next random note in the current sequence
int Scale::GetNextRandomNote()
{
	PrevNote = CurrentNote;  	// archive it
	do
	{
		// this method has a different character, keeping notes near keyboard center.
		// CurrentNote = NormalRandom(39,7);
		// for a truly uniform distribution (should sound terrible)
		// CurrentNote = random(88);

		CurrentNote = NormalRandom(PrevNote,StdDev);	// normal generation method.
		if((CurrentNote < 0) || (CurrentNote > 87))  	// ran out of bounds
			CurrentNote = CurrentKey + 36;				// approx. mid-keyboard and surely legal
	}
	while(!IsLegal(CurrentNote)); 	// just an arg: CurrentNote won't change
	return CurrentNote;
}          						//GetNextRandomNote
//----------------------------------------------------------------------------
// returns a note value 0-87 proportional to where x stands between bottom and top.
// it is a chromatic note, not restricted to any scale.
// typical (and anticipated) use is for Bif.exe where raw values are 0 to 1:
// TranslateToNote(x, 0, 1);
int Scale::TranslateToNote(double x, double bottom, double top)
{
	if(bottom > top)
		swap(bottom,top);
	if((x == bottom) || (bottom == top))	// at bottom, OR range is undeterminable (an error)
		return 0;
	if(x == top)
		return 87;
	return round((x - bottom)/(top - bottom) * 87);
}                 				//TranslateToNote
//----------------------------------------------------------------------------
// returns a note value taken from NoteArray that is
// proportional to where x stands between bottom and top.
// returns the note number, NOT an index into NoteArray.
int Scale::TranslateToScaleNote(double x, double bottom, double top)
{
	int N = NoteArray.GetItemsInContainer();
	if(!N)
		return 0;
	if(bottom > top)
		swap(bottom,top);
	if((x == bottom) || (bottom == top))	// at bottom, OR range is undeterminable (an error)
		return (int)NoteArray[0];
	if(x == top)
		return (int)NoteArray[N - 1];

	// #error unchecked and sloppy
	int index = round((x - bottom)/(top - bottom) * (N - 1));
	if((index < 0) || (index >= N))
	{
		logerror("index out of bounds");
		return (int)NoteArray[0];
	}
	return (int)NoteArray[index];
}                 				//TranslateToScaleNote
//----------------------------------------------------------------------------
// maps value to a sound in a linear scale with a pleasing range,
// proportional to where x stands between bottom and top.
// the range 50 to 2550 Hz is copied from bif.cpp where it was found to work well.
uint Scale::TranslateToSound(double x, double bottom, double top)
{
	if(bottom > top)
		swap(bottom,top);
	if((x == bottom) || (bottom == top))	// at bottom, OR range is undeterminable (an error)
		return 0;
	if(x == top)
		return 2550;
	return round((x - bottom)/(top - bottom) * 2500. + 50.);
}
//----------------------------------------------------------------------------
// 						end class Scale
//////////////////////////////////////////////////////////////////////////////

Random music C++ program

musicdos.cpp

/*	musicdos.cpp			2/17/03
	Copyright (C)1995, 2003 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

	Borland C++, MSDOS target.
	This was for experimenting generally with the DOS sound() function and random music generation,
	so it has several functions that are only barely related to each other. The most interesting
	is the random music generation, the output of which is surprisingly musical. There are
	several combinations of mode, tempo, and standard deviation that work particularly well.
	It has a few runtime switches, but I never got them organized, and usually just experimented
	with the source code (both here and in music.cpp) and recompiled.

------
To Do:

Random generation:
--if it doesn't allow rests, add it.
--Try to create the ability to use the "87" development techniques on a motive;
  randomly generate the motive; then repeat it using the various alterations
  in randomly selected combinations.

------
Notes:

7/7/04: Somewhere I had an idea about some alternative random generation methods.

*/
#include <stdio.h>
#include <stddef.h>
#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <math.h>
#include "c:\bcs\my.h"
#pragma hdrstop

#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\music.cpp"		/* has definition of Scale class */

//----------------------------------------------------------------------------
// Has all the code from the old heartest.cpp. Plays sounds through pc speaker.
// The speaker's range seems to be about 20 to 15,000
// randomwalk option was inspired by the question of what a stock price series would sound like.
int HearingTest()
{
BOOL randomwalk = FALSE;
int frequency = 440;
string helpline("<H>igher, <L>ower, (octave: <U>p or <D>own) <ESC>=quit <P>ause <R>andom");
cout << helpline << endl;

userinput("Enter starting frequency",frequency);
while(1)
{
	if(randomwalk)
		switch(random(3))
		{
			case 0: break;
			case 1: frequency = max(frequency - 1,0); break;
			case 2: frequency++; break;
		}
	cout << frequency << " ";
	sound(frequency);
	if(kbhit())
		switch(tolower(getch()))
		{
			case 'u':
				frequency *= 2;
				break;
			case 'd':
				frequency /= 2;
				break;
			case 'h':
				frequency++;
				break;
			case 'l':
				if(--frequency < 0)
					frequency = 0;
				break;
			case 'p':
				cout << helpline << endl;
				presskey();
				break;
			case 'r':
				randomwalk = !randomwalk;
				break;
			case 27:
				nosound();
				return(0);
		}
}
}							// HearingTest
//----------------------------------------------------------------------------
int PlayScale()
{
int i, frequency;
int sequence[] = {0,2,4,5,7,9,11,12,0,12,0,12,0,12,255};
//              a  a#   b   c  c#   d  d#   e   f  f#   g  g#   a
int scale[] = {220,233,247,262,277,294,311,330,349,370,392,415,440,0 };
//              0   1   2   3   4   5   6   7   8   9   10  11  12
puts("<ESC> quits");
puts("Any other key restarts");
do
{
	i = 0;
	while(sequence[i] != 255)
	{
		frequency = scale[sequence[i]];
		sound(frequency);
		delay(500);
		i++;
	}
	nosound();
}
while(getch() != 27);
return(0);
}						// PlayScale
//----------------------------------------------------------------------------
// play raw frequencies from data file.
int PlayFileFreqs()
{
int frequency;
ifstream infile("freqs.dat");
while(infile >> frequency)
{
	frequency *= 20;
	if(kbhit())
	{
		getch();
		break;
	}
	cout << frequency << endl;
	sound(frequency);
	delay(200);
	nosound();
}
return 0;
}
//----------------------------------------------------------------------------
// play the note numbers from data file.
int PlayFileNotes()
{
int note, prevnote = 0;
ifstream infile("notes.dat");
while(infile >> note)
{
	if(kbhit())
	{
		getch();
		break;
	}
	cout << note << endl;

	if((note < 1) || (note > 88))
		note = 40;

	if(note != prevnote)
		sound(27.5 * pow(ROOT12OF2, note));

	delay(100);
	prevnote = note;
}
nosound();
return 0;
}
//----------------------------------------------------------------------------
// generates random notes. This is the option that generates random music.
int RandomNotes()
{
Scale scale;
string helpline("<H>elp, <N>ew set, <I>nfo, <R>andomize periodically, <ESC>=quit");
cout << "While running, press:" << endl << helpline << endl;
presskey();
BOOL autorandomize = FALSE;     	// start false for the file output
ofstream outfile("notes.dat");
cout << scale.GetRandomInfo() << endl;
for(int j = 0 ; j < 10000 ; j++) 	// plays 10000 notes unless user stops it
{
	if(kbhit())
		switch(tolower(getch()))
		{
			case 'h':						// show help line
				cout << helpline << endl;
				presskey();
				break;
			case 'n':   					// new set (start using new key, mode, and std dev)
				scale.Randomize();			// falls thru to info
			case 'i':						// info
				cout << scale.GetRandomInfo() << endl;
				break;
			case 'r':                		// toggle periodic key/mode/stddev randomization
				autorandomize = !autorandomize;
				cout << "AutoRandomize = " << (autorandomize ? "ON" : "OFF") << endl;
				break;
			case 27:
			default:
				nosound();
				return(0);
		}

	if(autorandomize && !random(200))	// selects new key, mode, and std. dev. about every 200 notes
	{
		scale.Randomize();
		cout << scale.GetRandomInfo() << endl;
	}
	scale.GetNextRandomNote();
	// send the first 1024 notes generated to a data file
	if(j < 1024)								// limit to max for Fourier analysis in Excel
	{
		outfile << scale.CurrentNote << endl;
		cout << "*";							// indicates it was sent to file
	}
	cout << scale.CurrentNote << endl;
	if(scale.CurrentNote != scale.PrevNote)		// if it's the same note, just hold it over
		sound(scale.CurrentFreq());
	delay(scale.NoteLength);
}
nosound();
return 0;
}							//RandomNotes
//----------------------------------------------------------------------------
int main()
{
randomize();
while(1)
{
	char choice;
	do
	{
		cout << "-----------------------------------------------------------" << endl;
		cout << "0  Exit." << endl;
		cout << "1  Generate random tones (random music)" << endl;
		cout << "2  Play FREQUENCIES from file notes.dat" << endl;
		cout << "3  Play NOTES  from file notes.dat" << endl;
		cout << "4  Play scales" << endl;
		cout << "5  Hearing test" << endl;
		cout << endl << "Enter choice: ";
		choice = tolower(getche());
		cout << endl << endl;
	}
	while(!string("012345").contains(choice));
	if(choice == '0')
		break;				// Done.  break from while(1)
	switch(choice)
	{
		case '1': RandomNotes(); break;
		case '2': PlayFileFreqs(); break;
		case '3': PlayFileNotes(); break;
		case '4': PlayScale(); break;
		case '5': HearingTest(); break;
	}
}				// while(1)
return 0;
}							// main

 

If you've ever wanted to try composing music, Finale NotePad® is a terrific music notation program that you can download for free. Its user interface is the same as the well-known Finale®. It's fun to use. You can play your composition through your MIDI device, and can also print and save it. It has far fewer features than Finale®, but it's not a crippleware demo; it's a fully functional introduction to their music notation products.

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2007 Steven Whitney. Last modified 09/25/2007.