diff --git a/main.cpp b/main.cpp index d169a1d..85572b6 100644 --- a/main.cpp +++ b/main.cpp @@ -4,46 +4,134 @@ #include #include #include +#include #include void WriteWAVHeader(std::ofstream &file, DWORD length) { + // Ensure the file is open and valid before writing + if (!file.is_open()) { + // Handle error: file is not open + std::cerr << "Error: Output file is not open." << std::endl; + return; + } + DWORD dataChunkSize = length; - DWORD fmtChunkSize = 16; + DWORD fmtChunkSize = 16; // Size of the fmt chunk for PCM + + // Audio format properties (PCM, Stereo, 44100 Hz, 16 bits per sample) + WORD formatTag = 1; // PCM = 1 + WORD channels = 2; DWORD sampleRate = 44100; WORD bitsPerSample = 16; - WORD channels = 2; + + // Calculate byte rate and block align DWORD byteRate = sampleRate * channels * bitsPerSample / 8; WORD blockAlign = channels * bitsPerSample / 8; - DWORD totalSize = 4 + (8 + fmtChunkSize) + (8 + dataChunkSize); + // Calculate the size of the data in the RIFF chunk following the initial 8 bytes. + // This is the size of the "WAVE" identifier (4 bytes) + the size of the "fmt " chunk + // (8 bytes header + fmtChunkSize) + the size of the "data" chunk (8 bytes header + dataChunkSize). + // Total size = 4 (for "WAVE") + (8 + fmtChunkSize) + (8 + dataChunkSize) + // Since fmtChunkSize is 16 for PCM: + // Total size = 4 + (8 + 16) + (8 + dataChunkSize) + // Total size = 4 + 24 + 8 + dataChunkSize + // Total size = 36 + dataChunkSize + DWORD totalSize = 36 + dataChunkSize; + + + // RIFF chunk file.write("RIFF", 4); + // Write the total size of the file minus the first 8 bytes ("RIFF" and this size field) file.write(reinterpret_cast(&totalSize), 4); file.write("WAVE", 4); // fmt chunk - file.write("fmt ", 4); - file.write(reinterpret_cast(&fmtChunkSize), 4); - WORD formatTag = 1; // PCM - file.write(reinterpret_cast(&formatTag), 2); - file.write(reinterpret_cast(&channels), 2); - file.write(reinterpret_cast(&sampleRate), 4); - file.write(reinterpret_cast(&byteRate), 4); - file.write(reinterpret_cast(&blockAlign), 2); - file.write(reinterpret_cast(&bitsPerSample), 2); + file.write("fmt ", 4); // Note the trailing space + file.write(reinterpret_cast(&fmtChunkSize), 4); // Size of the fmt chunk (16 for PCM) + file.write(reinterpret_cast(&formatTag), 2); // Audio format (1 for PCM) + file.write(reinterpret_cast(&channels), 2); // Number of channels + file.write(reinterpret_cast(&sampleRate), 4); // Sample rate + file.write(reinterpret_cast(&byteRate), 4); // Byte rate + file.write(reinterpret_cast(&blockAlign), 2); // Block align + file.write(reinterpret_cast(&bitsPerSample), 2); // Bits per sample // data chunk - file.write("data", 4); - file.write(reinterpret_cast(&dataChunkSize), 4); + file.write("data", 4); // Note the trailing space is NOT present for "data" + file.write(reinterpret_cast(&dataChunkSize), 4); // Size of the data chunk + + // Note: For strict WAV compliance, if dataChunkSize is odd, a padding byte + // should be added here after the data chunk. This function only writes the header. +} + +const char* gm_instruments[128] = { + "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", + "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", + "Celesta", "Glockenspiel", "Music Box", "Vibraphone", + "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", + "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", + "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", + "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", + "Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", + "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", + "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", + "Violin", "Viola", "Cello", "Contrabass", + "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", + "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", + "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", + "Trumpet", "Trombone", "Tuba", "Muted Trumpet", + "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", + "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", + "Oboe", "English Horn", "Bassoon", "Clarinet", + "Piccolo", "Flute", "Recorder", "Pan Flute", + "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", + "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", + "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass + lead)", + "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", + "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", + "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", + "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", + "Sitar", "Banjo", "Shamisen", "Koto", + "Kalimba", "Bagpipe", "Fiddle", "Shanai", + "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", + "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", + "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", + "Telephone Ring", "Helicopter", "Applause", "Gunshot" +}; + +void ShowActiveInstruments(HSTREAM stream) { + for (int ch = 0; ch < 16; ++ch) { + DWORD prog = BASS_MIDI_StreamGetEvent(stream, ch, MIDI_EVENT_PROGRAM); + DWORD bank = BASS_MIDI_StreamGetEvent(stream, ch, MIDI_EVENT_BANK); + if (prog == -1 || prog >= 128) continue; + + const char* name = gm_instruments[prog]; + std::cout << "> [Ch " << std::setw(2) << std::setfill(' ') << (ch + 1) + << "] 🎶 " << name << " (Bank " << bank << ", Prog " << prog << ")\n"; + } +} + +void ClearLines(int count) { + for (int i = 0; i < count; ++i) { + std::cout << "\x1B[2K" << "\x1B[1A" << "\r"; // erase line + move up + } + std::cout << "\x1B[2K\r"; // erase current line } int main(int argc, char** argv) { std::cout << "Program start\n"; - if (argc != 4) { + if (argc < 3 || argc > 4) { std::cerr << "Wrong arguments!\n"; return -1; } - bool synthToFile = std::strcmp(argv[3], "true") == 0; + bool synthToFile = false; + + if (argc == 4) { + if (std::strcmp(argv[3], "synth") == 0) { + synthToFile = true; + } + } + // 1) Initialize BASS: default device, 44100 Hz, no flags if (!BASS_Init(-1, 44100, 0, NULL, NULL)) { std::cerr << "BASS_Init error: " << BASS_ErrorGetCode() << std::endl; @@ -83,6 +171,7 @@ int main(int argc, char** argv) { if (!synthToFile) { std::cout << "Playing live...\n"; + int loopCounter = 0; if (!BASS_ChannelPlay(midiStream, FALSE)) { std::cerr << "BASS_ChannelPlay error: " << BASS_ErrorGetCode() << std::endl; BASS_StreamFree(midiStream); @@ -90,9 +179,22 @@ int main(int argc, char** argv) { BASS_Free(); return 1; } + + std::thread monitorThread([&]() { + const int linesPerUpdate = 18; // 16 channels + header/footer + while (BASS_ChannelIsActive(midiStream) == BASS_ACTIVE_PLAYING) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + ClearLines(linesPerUpdate); + std::cout << "--- Active Instruments ---\n"; + ShowActiveInstruments(midiStream); + std::cout << "--------------------------\n"; + std::cout.flush(); + } + }); std::cout << "Press Enter to stop...\n"; std::cin.get(); // wait for user input + monitorThread.join(); } else { std::ofstream wav("output.wav", std::ios::binary); if (!wav) {