Skip to content

Instantly share code, notes, and snippets.

@FireDasher
Created September 16, 2025 23:01
Show Gist options
  • Save FireDasher/ee118b273c3fa69c249abdca8805fd03 to your computer and use it in GitHub Desktop.
Save FireDasher/ee118b273c3fa69c249abdca8805fd03 to your computer and use it in GitHub Desktop.
midi thing
#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;
}
#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