import struct
import random

class MIDIWriter:
    def __init__(self, ticks_per_beat=480):
        self.ticks_per_beat = ticks_per_beat
        self.events = []

    def _encode_varlen(self, val):
        buf = bytearray()
        buf.append(val & 0x7F)
        val >>= 7
        while val > 0:
            buf.insert(0, (val & 0x7F) | 0x80)
            val >>= 7
        return bytes(buf)

    def set_tempo(self, tick, bpm):
        tempo_us_per_beat = int(60_000_000 / bpm)
        data = bytes([0xFF, 0x51, 0x03, 
                      (tempo_us_per_beat >> 16) & 0xFF,
                      (tempo_us_per_beat >> 8) & 0xFF,
                      tempo_us_per_beat & 0xFF])
        self.events.append((tick, data))

    def set_time_signature(self, tick, nn=4, dd=2, cc=24, bb=8):
        data = bytes([0xFF, 0x58, 0x04, nn, dd, cc, bb])
        self.events.append((tick, data))

    def add_note(self, start_tick, duration_ticks, channel, note, velocity):
        velocity = max(1, min(127, int(velocity)))
        status_on = 0x90 | (channel & 0x0F)
        self.events.append((start_tick, bytes([status_on, note & 0x7F, velocity])))
        status_off = 0x80 | (channel & 0x0F)
        self.events.append((start_tick + duration_ticks, bytes([status_off, note & 0x7F, 0])))

    def save(self, filename):
        self.events.sort(key=lambda x: (x[0], x[1][0]))
        track_data = bytearray()
        last_tick = 0
        for tick, event_bytes in self.events:
            delta = tick - last_tick
            track_data.extend(self._encode_varlen(delta))
            track_data.extend(event_bytes)
            last_tick = tick

        track_data.extend(self._encode_varlen(0))
        track_data.extend(bytes([0xFF, 0x2F, 0x00]))

        header = struct.pack('>4sIHHH', b'MThd', 6, 0, 1, self.ticks_per_beat)
        track_header = struct.pack('>4sI', b'MTrk', len(track_data))

        with open(filename, 'wb') as f:
            f.write(header)
            f.write(track_header)
            f.write(track_data)

# Mapeo General MIDI de Batería
KICK = 36
SNARE = 38
CLOSED_HH = 42
OPEN_HH = 46
FLOOR_TOM_LOW = 41
FLOOR_TOM_HI = 43
MID_TOM_LOW = 45
MID_TOM_HI = 47
HIGH_TOM = 50
CRASH_1 = 49
CRASH_2 = 57
RIDE_1 = 51
RIDE_BELL = 53

random.seed(101)
writer = MIDIWriter(ticks_per_beat=480)
ch = 9  # Canal MIDI 10 (index 0-based)

# 76 BPM
writer.set_tempo(0, 76)
writer.set_time_signature(0, 4, 2)

PPQ = 480
BAR = 4 * PPQ
EIGHTH = PPQ // 2
SIXTEENTH = PPQ // 4

def h_time(tick):
    return max(0, tick + random.randint(-6, 6))

def h_vel(vel):
    return max(1, min(127, vel + random.randint(-5, 5)))

current_bar = 1
def advance(n):
    global current_bar
    st = current_bar
    current_bar += n
    return st

# 1. INTRO (8 compases)
sb = advance(8)
for b in range(sb, sb + 8):
    bt = (b - 1) * BAR
    if b == sb:
        writer.add_note(h_time(bt), PPQ * 2, ch, CRASH_1, h_vel(80))
    for i in range(16):
        t = bt + i * SIXTEENTH
        if i in [0, 3, 8, 11]:
            writer.add_note(h_time(t), SIXTEENTH, ch, KICK, h_vel(90 if i in [0, 8] else 75))
        if i in [2, 6, 10, 14]:
            writer.add_note(h_time(t), SIXTEENTH, ch, FLOOR_TOM_HI, h_vel(85))
        if i in [4, 12]:
            writer.add_note(h_time(t), SIXTEENTH, ch, MID_TOM_LOW, h_vel(90))
        if i in [7, 15]:
            writer.add_note(h_time(t), SIXTEENTH, ch, SNARE, h_vel(65))

# 2. ESTROFA 1 (8 compases)
sb = advance(8)
for b in range(sb, sb + 8):
    bt = (b - 1) * BAR
    if b == sb:
        writer.add_note(h_time(bt), PPQ * 2, ch, CRASH_1, h_vel(75))
    for e in range(8):
        writer.add_note(h_time(bt + e * EIGHTH), EIGHTH - 10, ch, CLOSED_HH, h_vel(70 if e % 2 == 0 else 55))
    if b < sb + 7:
        writer.add_note(h_time(bt + PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(82))
        writer.add_note(h_time(bt + 3 * PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(85))
    writer.add_note(h_time(bt), SIXTEENTH * 3, ch, KICK, h_vel(85))
    writer.add_note(h_time(bt + 2 * PPQ + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(80))
    if b == sb + 7:
        writer.add_note(h_time(bt + PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(85))
        t3 = bt + 2 * PPQ
        for idx, n in enumerate([SNARE, HIGH_TOM, MID_TOM_HI, FLOOR_TOM_HI, FLOOR_TOM_LOW, SNARE]):
            writer.add_note(h_time(t3 + idx * SIXTEENTH), SIXTEENTH, ch, n, h_vel(75 + idx * 6))

# 3. ESTROFA 2 (8 compases)
sb = advance(8)
for b in range(sb, sb + 8):
    bt = (b - 1) * BAR
    for e in range(8):
        writer.add_note(h_time(bt + e * EIGHTH), EIGHTH - 10, ch, CLOSED_HH, h_vel(80 if e % 2 == 0 else 65))
    writer.add_note(h_time(bt + PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(95))
    writer.add_note(h_time(bt + 3 * PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(98))
    writer.add_note(h_time(bt), SIXTEENTH * 3, ch, KICK, h_vel(95))
    writer.add_note(h_time(bt + PPQ + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(85))
    writer.add_note(h_time(bt + 2 * PPQ + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(90))
    if b == sb + 7:
        t3 = bt + 2 * PPQ
        for idx, n in enumerate([HIGH_TOM, MID_TOM_HI, MID_TOM_LOW, FLOOR_TOM_HI, FLOOR_TOM_LOW, SNARE]):
            writer.add_note(h_time(t3 + idx * SIXTEENTH), SIXTEENTH, ch, n, h_vel(85 + idx * 6))

# 4. CORO (8 compases)
sb = advance(8)
for b in range(sb, sb + 8):
    bt = (b - 1) * BAR
    if b in [sb, sb + 4]:
        writer.add_note(h_time(bt), PPQ * 2, ch, CRASH_1, h_vel(115))
        writer.add_note(h_time(bt), PPQ * 2, ch, CRASH_2, h_vel(100))
    for e in range(8):
        cym = RIDE_BELL if e in [0, 4] else RIDE_1
        writer.add_note(h_time(bt + e * EIGHTH), EIGHTH, ch, cym, h_vel(105 if e % 2 == 0 else 88))
    if b not in [sb + 3, sb + 7]:
        writer.add_note(h_time(bt + PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(112))
        writer.add_note(h_time(bt + 3 * PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(115))
    writer.add_note(h_time(bt), SIXTEENTH * 3, ch, KICK, h_vel(110))
    writer.add_note(h_time(bt + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(95))
    writer.add_note(h_time(bt + 2 * PPQ), SIXTEENTH * 3, ch, KICK, h_vel(105))
    writer.add_note(h_time(bt + 2 * PPQ + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(100))
    if b in [sb + 3, sb + 7]:
        t3 = bt + 2 * PPQ
        for idx in range(8):
            n = [SNARE, HIGH_TOM, MID_TOM_HI, MID_TOM_LOW, FLOOR_TOM_HI, FLOOR_TOM_LOW, SNARE, CRASH_1][idx]
            writer.add_note(h_time(t3 + idx * SIXTEENTH), SIXTEENTH, ch, n, h_vel(95 + idx * 4))

# 5. PUENTE (8 compases)
sb = advance(8)
for b in range(sb, sb + 8):
    bt = (b - 1) * BAR
    writer.add_note(h_time(bt), PPQ * 2, ch, CRASH_1, h_vel(110))
    for e in range(8):
        writer.add_note(h_time(bt + e * EIGHTH), EIGHTH, ch, OPEN_HH, h_vel(98))
    writer.add_note(h_time(bt + PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(115))
    writer.add_note(h_time(bt + 3 * PPQ), SIXTEENTH * 3, ch, SNARE, h_vel(118))
    writer.add_note(h_time(bt), SIXTEENTH * 3, ch, KICK, h_vel(115))
    writer.add_note(h_time(bt + EIGHTH), SIXTEENTH * 3, ch, KICK, h_vel(100))
    writer.add_note(h_time(bt + 2 * PPQ), SIXTEENTH * 3, ch, KICK, h_vel(110))

# Acorde final
ft = (current_bar - 1) * BAR
writer.add_note(h_time(ft), PPQ * 4, ch, CRASH_1, h_vel(124))
writer.add_note(h_time(ft), PPQ * 4, ch, KICK, h_vel(120))

writer.save("Hossana_Marco_Barrientos_Bateria.mid")
print("Archivo Hossana_Marco_Barrientos_Bateria.mid generado exitosamente.")

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: