const char * UsageLines [] = { "Usage: swnotes (beats per second) (sample rate) (key)", "Reads text describing notes from standard input.", "Writes headerless sound file to standard output.", "Reads whitespace-separated groups of the form:", "[(repetition)x](duration)[:(note)][,(note)]...", "All must be non-negative integers. Duration is measured in beats.", "A duration without notes will produce silence.", "Each note value must be between 0 and 72.", "For a key of 0, note 41 has a pitch of 440 Hz. Each higher/", "lower note number has a pitch one semitone higher/lower using", "equal temperament.", "Example of scale CDEFGABC: 1:44 1:46 1:48 1:49 1:51 1:53 1:55 1:56", "Example C-chord ECEGCE (lasting 10 beats, played 3 times):", "\t3x10:12,20,24,27,32,36", "Specifying a key other than 0 will shift the key upward (+)", "or downward (-) by the specified number of semitones.", "Sample rate is in Hz, for example 8000 for 8kHz.", "Writes mono, signed, two-byte-per-sample,", "\tLS byte first sound file (.sw).", "Anything from # to end of line is considered a comment.", "May 16, 2011. Newest is at gopher -p users/julianbr sdf.org", }; const int NumUsageLines = sizeof (UsageLines)/sizeof (UsageLines [0] ); /* Compile with -lm */ #include #include #define NumNotes 73 #define ReferenceNote 41 #define ReferenceNoteHz 440 #define RiseTimesPerBeat 2 void PlayChord ( int NumBeats, int * notes, unsigned long int BeatsPerSecond, unsigned long int SamplesPerSecond, int key) { unsigned long int sample, SamplesPerBeat, NumSamples, NoteHz; unsigned long int amplitude, AmplitudeEachNote; unsigned int AmplitudeThisNote, AmplitudeAllNotes; int NumUsedNotes, note; float shape; float log2over12; log2over12 = log (2)/12; NumUsedNotes = 0; for (note = 0; note < NumNotes; note++) NumUsedNotes += notes [note]; if (NumUsedNotes > 0) AmplitudeEachNote = 32767/NumUsedNotes; SamplesPerBeat = SamplesPerSecond/BeatsPerSecond; NumSamples = NumBeats*SamplesPerSecond/BeatsPerSecond; for (sample = 0; sample < NumSamples; sample++) { if (sample < SamplesPerBeat/RiseTimesPerBeat) shape = 1.0*sample /(SamplesPerBeat/RiseTimesPerBeat); else shape = 1.0*(NumSamples - sample) /(NumSamples - SamplesPerBeat/RiseTimesPerBeat); AmplitudeAllNotes = 0; for (note = 0; note < NumNotes; note++) { if (notes [note] > 0) { NoteHz = ReferenceNoteHz*exp ( (note + key - ReferenceNote)*log2over12) + .5; AmplitudeThisNote = AmplitudeEachNote*notes [note] *shape *sin(2*M_PI*((sample*NoteHz) %SamplesPerSecond) /SamplesPerSecond); AmplitudeAllNotes += AmplitudeThisNote; } } if (AmplitudeAllNotes > 0) amplitude = 32768 + AmplitudeAllNotes; else if (AmplitudeAllNotes < 0) amplitude = 32767 - AmplitudeAllNotes; else if (sample%2 == 0) amplitude = 32768; else amplitude = 32767; /* Two byte, little-endian, mono, signed (.sw) */ putchar (amplitude%256); putchar ((amplitude/256) ^ 128); } } int PlayChords ( unsigned long int BeatsPerSecond, unsigned long int SamplesPerSecond, int key) { int notes [NumNotes]; int c, NumRepetitions, NumBeats, note, i; c = getchar (); while (c == ' ' || c == '\t' || c == '\n') c = getchar (); while (c != EOF) { NumRepetitions = 0; while (c >= '0' && c <= '9') { NumRepetitions = 10*NumRepetitions + (c - '0'); c = getchar (); } if (c == 'x') { c = getchar (); NumBeats = 0; while (c >= '0' && c <= '9') { NumBeats = 10*NumBeats + (c - '0'); c = getchar (); } } else { NumBeats = NumRepetitions; NumRepetitions = 1; } if (c == ':') c = getchar (); note = 0; while (note < sizeof (notes)/sizeof (notes [0] ) ) { notes [note] = 0; note++; } while (c >= '0' && c <= '9') { note = 0; while (c >= '0' && c <= '9') { note = 10*note + (c - '0'); c = getchar (); } if (note >= sizeof (notes)/sizeof (note) ) { fprintf (stderr, "***swnotes: Improper note"); fprintf (stderr, " number: %d.\n", note); return 0; } notes [note]++; if (c == ',') c = getchar (); } if (c == '#') { /* Treat anything from # to end of line as a comment */ while (c != EOF && c != '\n') c = getchar (); } else if (!(c == EOF || c == ' ' || c == '\t' || c == '\n') ) { fprintf (stderr, "***swnotes: Improper input"); fprintf (stderr, " character: '%c'.\n", c); return 0; } for (i = 0; i < NumRepetitions; i++) PlayChord ( NumBeats, notes, BeatsPerSecond, SamplesPerSecond, key); while (c == ' ' || c == '\t' || c == '\n') c = getchar (); } return 1; } int main (int argc, char * argv [] ) { unsigned long int BeatsPerSecond, SamplesPerSecond; int i, key, ok; char c; if (argc < 2) { for (i = 0; i < NumUsageLines; i++) printf ("%s\n", UsageLines [i] ); } else if (argc == 4) { ok = 1; if (sscanf (argv [1], "%lu%c", & BeatsPerSecond, & c) != 1 || BeatsPerSecond < 1) { fprintf (stderr, "***swnotes: Expecting number > 0"); fprintf (stderr, " for beats per second, found"); fprintf (stderr, " \"%s\".\n", argv [1] ); ok = 0; } if (sscanf (argv [2], "%lu%c", & SamplesPerSecond, & c) != 1 || SamplesPerSecond < 1) { fprintf (stderr, "***swnotes: Expecting number > 0"); fprintf (stderr, " for samples per second, found"); fprintf (stderr, " \"%s\".\n", argv [2] ); ok = 0; } if (sscanf (argv [3], "%d%c", & key, & c) != 1) { fprintf (stderr, "***swnotes: Expecting number"); fprintf (stderr, " for key, found"); fprintf (stderr, " \"%s\".\n", argv [3] ); ok = 0; } if (ok) PlayChords (BeatsPerSecond, SamplesPerSecond, key); } else { fprintf (stderr, "Usage: swmix (beats per second)"); fprintf (stderr, " (samples per second) (key)\n"); } return 0; }