|
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++ classA 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).
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:
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:
|
|
/* 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 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
//////////////////////////////////////////////////////////////////////////////
/* 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. |
|
|
|
|
|
|