Created
          September 16, 2025 23:01 
        
      - 
      
 - 
        
Save FireDasher/ee118b273c3fa69c249abdca8805fd03 to your computer and use it in GitHub Desktop.  
    midi thing
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #include <iostream> | |
| #include <SFML/Audio.hpp> | |
| #include "MIDIParser.hpp" | |
| #define print(a) std::cout << a << std::endl | |
| float clamp(float x, float min, float max) { | |
| return ((x < min) ? min : ((x > max) ? max : x)); | |
| } | |
| void playMidi(const std::vector<Midi::Note>& notes, sf::SoundBuffer& buffer) { | |
| const unsigned int sampleRate = 44100; | |
| const float twoPi = 2.0f * 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844f; | |
| float totalDuration = 0.0f; | |
| for (const auto& note : notes) totalDuration = std::max(totalDuration, note.time + note.duration); | |
| std::vector<int16_t> samples(totalDuration * sampleRate, 0); | |
| std::vector<float> mix(totalDuration * sampleRate, 0); | |
| for (const auto& note : notes) { | |
| float freq = 440.0f * std::pow(2.0f, (note.note - 69) / 12.0f); | |
| size_t start = note.time * sampleRate; | |
| size_t end = (note.time + (std::floorf(freq * note.duration)/freq)) * sampleRate; | |
| for (size_t i = start; i < end && i < samples.size(); ++i) { | |
| float t = (float)(i - start) / sampleRate; | |
| float sample = note.volume * std::sinf(twoPi * freq * t); | |
| mix[i] += sample; | |
| } | |
| } | |
| float peak = 0.0f; | |
| // float easingRate = 10.0f; | |
| // float dt = 1.0f / sampleRate; | |
| for (const float& s : mix) peak = std::max(peak, std::fabsf(s)); | |
| float scale = (peak > 0) ? (32767.0f / peak) : 0.0f; | |
| for (size_t i = 0; i < samples.size(); ++i) samples[i] = mix[i] * scale; | |
| // for (size_t i = 0; i < samples.size(); ++i) { | |
| // peak = (peak - fabsf(mix[i])) * easingRate * dt + peak - easingRate * peak * dt; | |
| // samples[i] = clamp(mix[i] * (32767.0f / peak), -32767.0f, 32767.0f); | |
| // } | |
| const std::vector<sf::SoundChannel> channelMap = {sf::SoundChannel::Mono}; | |
| if (!buffer.loadFromSamples(samples.data(), samples.size(), 1, sampleRate, channelMap)) { | |
| std::cerr << "Failed to load from samples!" << std::endl; | |
| return; | |
| } | |
| } | |
| int main() { | |
| Midi::MidiFile mid = Midi::MidiFile("urmine.mid"); | |
| sf::SoundBuffer buffer; | |
| sf::Sound sound{buffer}; | |
| playMidi(mid.notes, buffer); | |
| print("Playing"); | |
| sound.play(); | |
| // sf::OutputSoundFile file; | |
| // if (!file.openFromFile("song.ogg", buffer.getSampleRate(), 1, buffer.getChannelMap())) { | |
| // std::cerr << "Failed to open file!" << std::endl; | |
| // return 1; | |
| // }; | |
| // file.write(buffer.getSamples(), buffer.getSampleCount()); | |
| while (sound.getStatus() == sf::SoundSource::Status::Playing) { | |
| sf::sleep(sf::milliseconds(100)); | |
| } | |
| return 0; | |
| } | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #include <fstream> | |
| #include <cstdint> | |
| #include <vector> | |
| #include <string> | |
| #include <iostream> | |
| #include <unordered_map> | |
| namespace Midi { | |
| // MIDI note | |
| struct Note { | |
| // This note_on message's absolute time | |
| float time = 0.0f; | |
| // Time between this note_on message and the corrosponding note_off message | |
| float duration = 0.0f; | |
| // This note_on message's velocity divided by 128 | |
| float volume = 0.0f; | |
| // The program of the last program_change message before this note_on message | |
| int instrument = 0; | |
| // This note_on message's note | |
| int note = 0; | |
| // A function for printing the note | |
| std::string str() { | |
| return "MidiNote{{ Time: " + std::to_string(time) + ", Duration: " + std::to_string(duration) + ", Volume: " + std::to_string(volume) + ", Instrument: " + std::to_string(instrument) + ", Note: " + std::to_string(note) + " }}"; | |
| } | |
| }; | |
| // A class for parsing MIDI 1.0 files | |
| class MidiFile { | |
| public: | |
| std::vector<Note> notes; | |
| MidiFile(const std::string& path) { | |
| std::ifstream file(path, std::ios::binary); | |
| if (!file.is_open()) { | |
| std::cerr << "Error opening MIDI file." << std::endl; | |
| return; | |
| } | |
| uint32_t MThd = read<uint32_t>(file); | |
| if (MThd != 0x4D'54'68'64) { | |
| std::cerr << "Error file is not a MIDI file." << std::endl; | |
| return; | |
| } | |
| uint32_t header_length = read<uint32_t>(file); | |
| if (header_length != 6) { | |
| std::cerr << "Unexpected header length " << header_length << std::endl; | |
| return; | |
| } | |
| uint16_t format = read<uint16_t>(file); | |
| uint16_t ntracks = read<uint16_t>(file); | |
| uint16_t division = read<uint16_t>(file); | |
| float ticksPerBeat = (float)division; | |
| for (uint16_t trackn = 0; trackn < ntracks; ++trackn) { | |
| uint32_t MTrk = read<uint32_t>(file); | |
| if (MTrk != 0x4D'54'72'6B) { | |
| std::cerr << "Error invalid track MTrk is " << std::hex << std::uppercase << MTrk << std::endl; | |
| continue; | |
| } | |
| uint32_t track_length = read<uint32_t>(file); | |
| std::streampos start = file.tellg(); | |
| uint8_t last_status = 0; | |
| float currentTime = 0; | |
| std::vector<int> currentProgram(16, 0); | |
| std::unordered_map<uint16_t, Note> ongoingNotes{}; | |
| while (true) { | |
| if (file.tellg() - start == track_length) break; | |
| uint32_t delta = readVLQ(file); | |
| currentTime += (float)delta * (0.5f / ticksPerBeat); // 500000.0f (tempo) * 1e-6f = 0.5f | |
| uint8_t status = readByte(file); | |
| if (status < 0x80) { | |
| status = last_status; | |
| } else if (status != 0xFF) { | |
| // Meta messages don't set running status | |
| last_status = status; | |
| } | |
| if (status == 0xFF) { | |
| // Meta message, just skip over it | |
| uint8_t metatype = readByte(file); | |
| uint32_t length = readVLQ(file); | |
| file.seekg(length, std::ios::cur); | |
| } else if (status == 0xF0 || status == 0xF7) { | |
| // Sysex message, just skip over it | |
| uint32_t length = readVLQ(file); | |
| file.seekg(length, std::ios::cur); | |
| } else { | |
| uint8_t statusType = status & 0xF0; | |
| uint8_t data1 = 0; | |
| uint8_t data2 = 0; | |
| if (statusType == 0x80 || statusType == 0x90 || statusType == 0xA0 || statusType == 0xB0 || statusType == 0xE0 || status == 0xF2) { | |
| data1 = readByte(file); | |
| data2 = readByte(file); | |
| } else if (statusType == 0xC0 || statusType == 0xD0 || status == 0xF1 || status == 0xF3) { | |
| data1 = readByte(file); | |
| } | |
| if (statusType == 0x90 && data2 != 0) { | |
| // Note on | |
| uint8_t channel = status & 0x0F; | |
| uint16_t id = ((uint16_t)channel << 8) | (uint16_t)data1; | |
| Note note { | |
| .time = currentTime, | |
| .volume = (float)data2 / 128.0f, | |
| .instrument = currentProgram[channel], | |
| .note = data1, | |
| }; | |
| ongoingNotes[id] = note; | |
| } else if (statusType == 0x80 || (statusType == 0x90 && data2 == 0)) { | |
| // Note off | |
| uint8_t channel = status & 0x0F; | |
| uint16_t id = ((uint16_t)channel << 8) | (uint16_t)data1; | |
| Note note = ongoingNotes[id]; | |
| note.duration = currentTime - note.time; | |
| ongoingNotes.erase(id); | |
| this->notes.push_back(note); | |
| } else if (statusType == 0xC0) { | |
| // Program change | |
| uint8_t channel = status & 0x0F; | |
| currentProgram[channel] = data1; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private: | |
| // Read big endian | |
| template<typename T> static T read(std::ifstream& file) { | |
| T data = 0; | |
| for (size_t byte = 0; byte < sizeof(T); ++byte) { | |
| data = (data << 8) | file.get(); | |
| } | |
| return data; | |
| } | |
| // Read variable length quantity | |
| static uint32_t readVLQ(std::ifstream& file) { | |
| uint32_t value = 0; | |
| while (true) { | |
| uint8_t byte = file.get(); | |
| value = (value << 7) | (byte & 0x7F); | |
| if (byte < 0x80) return value; | |
| } | |
| } | |
| // Read one byte | |
| inline static uint8_t readByte(std::ifstream& file) { | |
| return file.get(); | |
| } | |
| }; | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment