// // this is bandclass2.cc (Band Class) // a simple music maker program which is basically my retarded version of a software synthesizer // taking concepts from the Yamaha SID chip's ADSR envelope generator // // copyright(c) 2008 by christopher abad // aempirei@gmail.com // // g++ bandclass2.cc -Wall -lm -o bandclass2 // // ./bandclass2 < filename.music > output.wav // #ifdef _WIN32 #include #include #define _USE_MATH_DEFINES typedef int ssize_t; double rint(double x) { int y=(int)x; return (x - y >= 0.5) ? x+1 : x; } #else #include #endif #include #include #include #include #include #include #include using namespace std; struct ltstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; class sample { public: double *data; unsigned int length; sample() { data = NULL; length = 0; } void set(unsigned int newLength) { if(data) if(newLength == length) return; else delete[] data; length = newLength; data = new double[length]; } signed short getLevel(unsigned int t) { double power = data[t]; signed long llevel = (int)rint(power * 65535 - 32768); assert(llevel >= -32768 && llevel <= 32767); return (signed short)llevel; } }; class envelope { public: const static unsigned int bins = 8; const static unsigned int levels = 8; unsigned int data[bins]; envelope() { for(unsigned int i = 0; i < bins; i++) data[i] = levels; } void set(unsigned int bin, unsigned int level) { assert(bin >= 0 && bin < bins); assert(level >= 0 && level <= levels); // levels actually is [0,levels], not [0,levels - 1] data[bin] = level; } void setAll(unsigned int *allLevels) { for(unsigned int i = 0; i < bins; i++) { set(i, allLevels[i]); } } void apply(sample *s) { // this applies our envelope convolution on our time series sample data // on half open intervals [0,levels) because it makes the math simpler and // im too high on drugs and lazy to make the envelope a closed interval for(unsigned int t = 0; t < s->length; t++) { // linear remap domain (i) to [0,levels - 1) double x = (double)(bins - 1) * (double)t / (double)s->length; // calculate relative distance ratio between two adjacent bins unsigned int leftbin = (int)rint(floor(x)); double ratio = x - leftbin; // calculate the convolution coefficient for the current f(t) as a convex combination of // a part of the amplitudes of the left and right bin adjacent to the current t // double coef = (1.0 - ratio) * (double)data[leftbin] + ratio * (double)data[leftbin + 1]; double coef = ratio * (data[leftbin + 1] - (double)data[leftbin]) + (double)data[leftbin]; // apply s->data[t] *= coef / (double)levels; } } }; class wave { private: double coef; public: unsigned long rate; double frequency; wave() { frequency = 2600; rate = 44100; set(); } void set() { coef = 2.0 * M_PI * (double)frequency / (double)rate; } double calculate(unsigned long t) { double theta = (double)t * coef; /* calculate the signal power */ double power = (sin(theta) + 1.0) / 2.0; return power; /* linear remap of the signal power to the range of a signed short (PCM S16LE) */ } }; class note { // our duration is in 8th notes (a duration of 8 would be 1 whole note) // but tempo is in quarter notes per minute private: static double key; // this is the base key frequency of the instrument (16.35 is octave-0 C) typedef map n2n; n2n noteToNumber; double calculateFrequency(int noteNumber, unsigned int octave) { return key * pow(2, octave + (double)noteNumber / 12.0); } public: envelope e; sample s; wave w; unsigned int tempo; // this is quarter notes per minute note() { noteToNumber["-"] = -1; noteToNumber["c"] = 0; noteToNumber["c#"] = 1; noteToNumber["db"] = 1; noteToNumber["d"] = 2; noteToNumber["d#"] = 3; noteToNumber["eb"] = 3; noteToNumber["e"] = 4; noteToNumber["f"] = 5; noteToNumber["f#"] = 6; noteToNumber["gb"] = 6; noteToNumber["g"] = 7; noteToNumber["g#"] = 8; noteToNumber["ab"] = 8; noteToNumber["a"] = 9; noteToNumber["a#"] = 10; noteToNumber["bb"] = 10; noteToNumber["b"] = 11; tempo = 60; } int getNoteNumber(const char *note) { n2n::iterator iter = noteToNumber.find(note); assert(iter != noteToNumber.end() /* invalid note */); return noteToNumber[note]; } int calculateSampleSize(int duration) { return duration * (w.rate * 60) / tempo / 2; } void generate(int noteNumber, unsigned int octave, unsigned int duration) { s.set(calculateSampleSize(duration)); if(noteNumber == -1) { // process a rest // for(unsigned int t = 0; t < s.length; t++) s.data[t] = 0; } else { // process an actual note w.frequency = calculateFrequency(noteNumber, octave); w.set(); for(unsigned int t = 0; t < s.length; t++) s.data[t] = w.calculate(t); e.apply(&s); } } }; double note::key = 16.35; #ifndef _WIN32 ssize_t write2(int fd, void *buf, size_t count) { size_t nleft, nwrite; ssize_t n; nleft = count; nwrite = 0; do { n = write(fd, (char *)buf + nwrite, nleft); if (n == 0) return (nwrite); else if (n == -1) return (-1); nwrite += n; nleft -= n; } while (nleft); return (nwrite); } #endif void print32(unsigned long v) { ssize_t n = fwrite(&v, 1, sizeof(v), stdout); assert(n == sizeof(v)); } void print16(unsigned short v) { ssize_t n = fwrite(&v, 1, sizeof(v), stdout); assert(n = sizeof(v)); } int main(int argc, char **argv) { note no; unsigned int tempo; unsigned int level; int n; unsigned long b32; unsigned short b16; FILE *fp; long length; unsigned char *s; assert(sizeof(b16) == 2); assert(sizeof(b32) == 4); if (argc != 1) { printf("\nusage: %s < music > output.s16le\n\n", *argv); exit(EXIT_FAILURE); } #ifdef _WIN32 setmode(fileno(stdout), O_BINARY); #endif no.w.rate = 22050; n = scanf("tempo = %u ", &tempo); assert(n == 1 && tempo > 0 /* tempo must be correctly set */); no.tempo = tempo; n = scanf("envelope = "); for(unsigned int i = 0; i < envelope::bins; i++) { n = scanf("%u ", &level); assert(n == 1 && level >= 0 && level <= envelope::levels /* invalid envelop level */); no.e.set(i, level); } // // this is the main loop. read a note, generate it and repeat // fp = tmpfile(); n = scanf("music = "); for(;;) { char note[8]; unsigned int duration, octave; int noteNumber; signed short *pcm; if(feof(stdin)) break; n = scanf("%u%4[aAbBcCdDeEfFgG#-]%u ", &duration, note, &octave); assert(n == 3); noteNumber = no.getNoteNumber(note); fprintf(stderr, "generated note %s (%i) duration %u octave %u with sample length of %i\n", note, noteNumber, duration, octave, no.s.length); no.generate(noteNumber, octave, duration); pcm = new signed short[no.s.length]; for(unsigned int t = 0; t < no.s.length; t++) pcm[t] = no.s.getLevel(t); n = fwrite(pcm, sizeof(signed short), no.s.length, fp); assert(n == (int)no.s.length); delete pcm; } length = ftell(fp); rewind(fp); // write the wav header fputs("RIFF", stdout); // magic print32(36 + length); // chunksize fputs("WAVE", stdout); // format fputs("fmt ", stdout); // subchunk id print32(16); // subchunk size print16(1); // audioformat (pcm) print16(1); // channels (mono) print32(no.w.rate); // samplerate print32(no.w.rate * 1 * 2); // byterate print16(1 * 2); // blockalign print16(16); // bitspersample fputs("data", stdout); // subchunk id print32(length); // subchunk size // write the wav content s = new unsigned char[length]; n = fread(s, 1, length, fp); assert(n == length); fclose(fp); n = fwrite(s, 1, length, stdout); assert(n == length); exit(EXIT_SUCCESS); }