#include #include #include #include #include #include /* SIMPLE CAVE STORY MUSIC PLAYER (Organya) */ /* Written by Joel Yliluoma -- http://iki.fi/bisqwit/ */ /* NX-Engine source code was used as reference. */ /* Cave Story and its music were written by Pixel ( 天谷 大輔 ) */ /* Copyright (C) 2011-2018 Joel Yliluoma This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Joel Yliluoma bisqwit@iki.fi */ //========= PART 0 : INPUT/OUTPUT AND UTILITY ========// using std::fgetc; int fgetw(FILE* fp) { int a = fgetc(fp), b = fgetc(fp); return (b<<8) + a; } int fgetd(FILE* fp) { int a = fgetw(fp), b = fgetw(fp); return (b<<16) + a; } double fgetv(FILE* fp) // Load a numeric value from text file; one per line. { char Buf[4096], *p=Buf; Buf[4095]='\0'; if(!std::fgets(Buf, sizeof(Buf)-1, fp)) return 0.0; // Ignore empty lines. If the line was empty, try next line. if(!Buf[0] || Buf[0]=='\r' || Buf[0]=='\n') return fgetv(fp); while(*p && *p++ != ':') {} // Skip until a colon character. return std::strtod(p, 0); // Parse the value and return it. } //========= PART 1 : SOUND EFFECT PLAYER (PXT) ========// static signed char Waveforms[6][256]; void GenerateWaveforms() { /* Six simple waveforms are used as basis for the signal generators in PXT: */ for(unsigned seed=0, i=0; i<256; ++i) { /* These waveforms are bit-exact with PixTone v1.0.3. */ seed = (seed * 214013) + 2531011; // Linear congruential generator Waveforms[0][i] = 0x40 * std::sin(i * 3.1416 / 0x80); // Sine Waveforms[1][i] = ((0x40+i) & 0x80) ? 0x80-i : i; // Triangle Waveforms[2][i] = -0x40 + i/2; // Sawtooth up Waveforms[3][i] = 0x40 - i/2; // Sawtooth down Waveforms[4][i] = 0x40 - (i & 0x80); // Square Waveforms[5][i] = (signed char)(seed >> 16) / 2; // Pseudorandom } } struct Pxt { struct Channel { bool enabled; int nsamples; // Waveform generator struct Wave { const signed char* wave; double pitch; int level, offset; }; Wave carrier; // The main signal to be generated. Wave frequency; // Modulator to the main signal. Wave amplitude; // Modulator to the main signal. // Envelope generator (controls the overall amplitude) struct Env { int initial; // initial value (0-63) struct { int time, val; } p[3]; // time offset & value, three of them int Evaluate(int i) const // Linearly interpolate between the key points: { int prevval = initial, prevtime=0; int nextval = 0, nexttime=256; for(int j=2; j>=0; --j) if(i < p[j].time) { nexttime=p[j].time; nextval=p[j].val; } for(int j=0; j<=2; ++j) if(i >=p[j].time) { prevtime=p[j].time; prevval=p[j].val; } if(nexttime <= prevtime) return prevval; return (i-prevtime) * (nextval-prevval) / (nexttime-prevtime) + prevval; } } envelope; // Synthesize the sound effect. std::vector Synth() { if(!enabled) return {}; std::vector result(nsamples); auto& c = carrier, &f = frequency, &a = amplitude; double mainpos = c.offset, maindelta = 256*c.pitch/nsamples; for(size_t i=0; i DrumSamples[12]; void LoadWaveTable() { FILE* fp = std::fopen("data/wavetable.dat", "rb"); if(!fp) { std::perror("data/wavetable.dat"); return; } for(size_t a=0; a<100*256; ++a) WaveTable[a] = (signed char) fgetc(fp); std::fclose(fp); } void LoadDrums() { GenerateWaveforms(); /* List of PXT files containing these percussion instruments: */ static const int patch[] = {0x96,0,0x97,0, 0x9a,0x98,0x99,0, 0x9b,0,0,0}; for(unsigned drumno=0; drumno<12; ++drumno) { if(!patch[drumno]) continue; // Leave that non-existed drum file unloaded // Load the drum parameters char Buf[64]; std::sprintf(Buf, "data/fx%02x.pxt", patch[drumno]); FILE* fp = std::fopen(Buf, "rb"); if(!fp) { std::perror(Buf); continue; } Pxt d; d.Load(fp); std::fclose(fp); // Synthesize and mix the drum's component channels auto& sample = DrumSamples[drumno]; for(auto& c: d.channels) { auto buf = c.Synth(); if(buf.size() > sample.size()) sample.resize(buf.size()); for(size_t a=0; a events; // Volatile data, used & changed during playback: double phaseacc, phaseinc, cur_vol; int cur_pan, cur_length, cur_wavesize; const short* cur_wave; } ins[16]; void Load(const char* fn) { FILE* fp = std::fopen(fn, "rb"); for(int i=0; i<6; ++i) fgetc(fp); // Ignore file signature ("Org-02") // Load song parameters ms_per_beat = fgetw(fp); /*steps_per_bar =*/fgetc(fp); // irrelevant /*beats_per_step=*/fgetc(fp); // irrelevant loop_start = fgetd(fp); loop_end = fgetd(fp); // Load each instrument parameters (and initialize them) for(auto& i: ins) i = { fgetw(fp), fgetc(fp), fgetc(fp)!=0, fgetw(fp), {}, 0,0,0,0,0,0,0 }; // Load events for each instrument for(auto& i: ins) { std::vector> events( i.n_events ); for(auto& n: events) n.first = fgetd(fp); for(auto& n: events) n.second.note = fgetc(fp); for(auto& n: events) n.second.length = fgetc(fp); for(auto& n: events) n.second.volume = fgetc(fp); for(auto& n: events) n.second.panning = fgetc(fp); i.events.insert(events.begin(), events.end()); } std::fclose(fp); } void Synth(unsigned sampling_rate, FILE* output) { // Determine playback settings: double samples_per_millisecond = sampling_rate * 1e-3, master_volume = 4e-6; int samples_per_beat = ms_per_beat * samples_per_millisecond; // rounded. // Begin synthesis int cur_beat = 0, total_beats=0; for(;; ++cur_beat) { if(cur_beat == loop_end) cur_beat = loop_start; fprintf(stderr, "[%d (%g seconds)] \r", cur_beat, total_beats++*samples_per_beat/double(sampling_rate)); // Synthesize this beat in stereo sound (two channels). std::vector result( samples_per_beat * 2, 0.f ); for(auto &i: ins) { // Check if there is an event for this beat auto j = i.events.find(cur_beat); if(j != i.events.end()) { auto& event = j->second; if(event.volume != 255) i.cur_vol = event.volume * master_volume; if(event.panning != 255) i.cur_pan = event.panning; if(event.note != 255) { // Calculate the note's wave data sampling frequency (equal temperament) double freq = std::pow(2.0, (event.note + i.tuning/1000.0 + 155.376) / 12); // Note: 155.376 comes from: // 12*log(256*440)/log(2) - (4*12-3-1) So that note 4*12-3 plays at 440 Hz. // Note: Optimizes into // pow(2, (note+155.376 + tuning/1000.0) / 12.0) // 2^(155.376/12) * exp( (note + tuning/1000.0)*log(2)/12 ) // i.e. 7901.988*exp(0.057762265*(note + tuning*1e-3)) i.phaseinc = freq / sampling_rate; i.phaseacc = 0; // And determine the actual wave data to play i.cur_wave = &WaveTable[256 * (i.wave % 100)]; i.cur_wavesize = 256; i.cur_length = i.pi ? 1024/i.phaseinc : (event.length * samples_per_beat); if(&i >= &ins[8]) // Percussion is different { const auto& d = DrumSamples[i.wave % 12]; i.phaseinc = event.note * (22050/32.5) / sampling_rate; // Linear frequency i.cur_wave = &d[0]; i.cur_wavesize = d.size(); i.cur_length = d.size() / i.phaseinc; } // Ignore missing drum samples if(i.cur_wavesize <= 0) i.cur_length = 0; } } // Generate wave data. Calculate left & right volumes... auto left = (i.cur_pan > 6 ? 12 - i.cur_pan : 6) * i.cur_vol; auto right = (i.cur_pan < 6 ? i.cur_pan : 6) * i.cur_vol; int n = samples_per_beat > i.cur_length ? i.cur_length : samples_per_beat; for(int p=0; p double { if(d == 0.) return 1.; if(std::fabs(d) > radius) return 0.; double dr = (d *= 3.14159265) / radius; return std::sin(d) * std::sin(dr) / (d*dr); }; double scale = 1/i.phaseinc > 1 ? 1 : 1/i.phaseinc, density = 0, sample = 0; int min = -radius/scale + pos - 0.5; int max = radius/scale + pos + 0.5; for(int m=min; m 0.) sample /= density; // Normalize // Save audio in float32 format: result[p*2 + 0] += sample * left; result[p*2 + 1] += sample * right; i.phaseacc += i.phaseinc; } i.cur_length -= n; } std::fwrite(&result[0], 4, result.size(), output); std::fflush(output); } } } song; int main(int argc, char** argv) /* どうくつ ものがたり */ { LoadWaveTable(); LoadDrums(); song.Load(argv[1]); FILE* fp = popen("aplay -fdat -fFLOAT_LE", "w"); /* Send audio to aplay */ song.Synth(48000, fp); // Play audio pclose(fp); }