├── .gitignore ├── README ├── __init__.py ├── base └── makefile.mk ├── bootloader ├── bootloader.c └── makefile └── tools ├── README ├── __init__.py ├── hex2sysex ├── __init__.py └── hex2sysex.py ├── hexfile ├── __init__.py └── hexfile.py └── midi ├── __init__.py └── midifile.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Precompiled python modules 2 | *.pyc 3 | 4 | # OS X crap 5 | .DS_Stor? 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | No dependency version of the Mutable Instruments MIDI bootloader for AVR. 2 | 3 | Things to mess with: 4 | * Path to the avr toolchain and avrdude in base/makefile.mk (TOOLCHAIN_PATH = ) 5 | * Programmer name in base/makefile.mk (PROGRAMMER = ). The programmer is expected to be connected to USB. 6 | * External quartz frequency in base/makefile.mk (F_CPU = ). 7 | * MCU type in bootloader/makefile (MCU_NAME = ) 8 | * Start address of bootloader code in the "text=0xfc00" line of bootloader/makefile. For example, ATMega644p flash size = 64k - 1k large bootloader => start address of 63k = 0xfc00. 9 | * MCU fuses in bootloader/makefile 10 | * I/O code for displaying LED patterns and deciding whether or not to enter the bootloader RX mode in bootloader/bootloader.c 11 | 12 | 13 | Setting up the fuses 14 | -------------------- 15 | 16 | make -f bootloader/makefile fuses 17 | 18 | 19 | Building the bootloader 20 | ----------------------- 21 | 22 | make -f bootloader/makefile 23 | 24 | 25 | Uploading the bootloader code to the MCU 26 | ---------------------------------------- 27 | 28 | make -f bootloader/makefile upload 29 | 30 | 31 | Producing a SysEx file from a firmware .hex 32 | ------------------------------------------- 33 | 34 | python tools/hex2sysex/hex2sysex.py --syx -o firmware.syx firmware.hex 35 | 36 | Check python tools/hex2sysex/hex2sysex.py -help for more options. In particular, you can change the page size which is configured, by default, to 256 bytes (for the ATMega644p and 1284p). To know the page size (in bytes) of a specific device, look up the value of SPM_PAGE_SIZE. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/avr-midi-bootloader/baf39f16648a6bf865df3251d1bd66fcb0572fa9/__init__.py -------------------------------------------------------------------------------- /base/makefile.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2009 Emilie Gillet. 2 | # 3 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | TOOLCHAIN_PATH = /usr/local/CrossPack-AVR/bin/ 17 | TOOLCHAIN_ETC_PATH = /usr/local/CrossPack-AVR/etc/ 18 | BUILD_ROOT = build/ 19 | BUILD_DIR = $(BUILD_ROOT)$(TARGET)/ 20 | PROGRAMMER = avrispmkII 21 | 22 | MCU = atmega$(MCU_NAME)p 23 | DMCU = m$(MCU_NAME)p 24 | MCU_DEFINE = ATMEGA$(MCU_NAME)P 25 | F_CPU = 20000000 26 | 27 | VPATH = $(PACKAGES) 28 | CC_FILES = $(notdir $(wildcard $(patsubst %,%/*.cc,$(PACKAGES)))) 29 | C_FILES = $(notdir $(wildcard $(patsubst %,%/*.c,$(PACKAGES)))) 30 | AS_FILES = $(notdir $(wildcard $(patsubst %,%/*.as,$(PACKAGES)))) 31 | OBJ_FILES = $(CC_FILES:.cc=.o) $(C_FILES:.c=.o) $(AS_FILES:.S=.o) 32 | OBJS = $(patsubst %,$(BUILD_DIR)%,$(OBJ_FILES)) 33 | DEPS = $(OBJS:.o=.d) 34 | 35 | TARGET_BIN = $(BUILD_DIR)$(TARGET).bin 36 | TARGET_ELF = $(BUILD_DIR)$(TARGET).elf 37 | TARGET_HEX = $(BUILD_DIR)$(TARGET).hex 38 | TARGETS = $(BUILD_DIR)$(TARGET).* 39 | DEP_FILE = $(BUILD_DIR)depends.mk 40 | 41 | CC = $(TOOLCHAIN_PATH)avr-gcc 42 | CXX = $(TOOLCHAIN_PATH)avr-g++ 43 | OBJCOPY = $(TOOLCHAIN_PATH)avr-objcopy 44 | OBJDUMP = $(TOOLCHAIN_PATH)avr-objdump 45 | AR = $(TOOLCHAIN_PATH)avr-ar 46 | SIZE = $(TOOLCHAIN_PATH)avr-size 47 | NM = $(TOOLCHAIN_PATH)avr-nm 48 | AVRDUDE = $(TOOLCHAIN_PATH)avrdude 49 | REMOVE = rm -f 50 | CAT = cat 51 | 52 | CPPFLAGS = -mmcu=$(MCU) -I. \ 53 | -g -Os -w -Wall \ 54 | -DF_CPU=$(F_CPU) \ 55 | -fdata-sections \ 56 | -ffunction-sections \ 57 | -fshort-enums \ 58 | -fno-move-loop-invariants \ 59 | $(EXTRA_DEFINES) \ 60 | $(MMC_CONFIG) \ 61 | -D$(MCU_DEFINE) \ 62 | -mcall-prologues 63 | CXXFLAGS = -fno-exceptions 64 | ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp 65 | LDFLAGS = -mmcu=$(MCU) -lm -Os -Wl,--gc-sections$(EXTRA_LD_FLAGS) 66 | 67 | # ------------------------------------------------------------------------------ 68 | # Source compiling 69 | # ------------------------------------------------------------------------------ 70 | 71 | $(BUILD_DIR)%.o: %.cc 72 | $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< -o $@ 73 | 74 | $(BUILD_DIR)%.o: %.c 75 | $(CC) -c $(CPPFLAGS) $(CXXFLAGS) $< -o $@ 76 | 77 | $(BUILD_DIR)%.o: %.s 78 | $(CC) -c $(CPPFLAGS) $(ASFLAGS) $< -o $@ 79 | 80 | $(BUILD_DIR)%.d: %.cc 81 | $(CXX) -MM $(CPPFLAGS) $(CXXFLAGS) $< -MF $@ -MT $(@:.d=.o) 82 | 83 | $(BUILD_DIR)%.d: %.c 84 | $(CC) -MM $(CPPFLAGS) $(CXXFLAGS) $< -MF $@ -MT $(@:.d=.o) 85 | 86 | $(BUILD_DIR)%.d: %.s 87 | $(CC) -MM $(CPPFLAGS) $(ASFLAGS) $< -MF $@ -MT $(@:.d=.o) 88 | 89 | 90 | # ------------------------------------------------------------------------------ 91 | # Object file conversion 92 | # ------------------------------------------------------------------------------ 93 | 94 | $(BUILD_DIR)%.hex: $(BUILD_DIR)%.elf 95 | $(OBJCOPY) -O ihex -R .eeprom $< $@ 96 | 97 | $(BUILD_DIR)%.bin: $(BUILD_DIR)%.elf 98 | $(OBJCOPY) -O binary -R .eeprom $< $@ 99 | 100 | $(BUILD_DIR)%.eep: $(BUILD_DIR)%.elf 101 | -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ 102 | --change-section-lma .eeprom=0 -O ihex $< $@ 103 | 104 | $(BUILD_DIR)%.lss: $(BUILD_DIR)%.elf 105 | $(OBJDUMP) -h -S $< > $@ 106 | 107 | $(BUILD_DIR)%.sym: $(BUILD_DIR)%.elf 108 | $(NM) -n $< > $@ 109 | 110 | # ------------------------------------------------------------------------------ 111 | # AVRDude 112 | # ------------------------------------------------------------------------------ 113 | 114 | AVRDUDE_CONF = $(TOOLCHAIN_ETC_PATH)avrdude.conf 115 | AVRDUDE_COM_OPTS = -V -p $(DMCU) 116 | AVRDUDE_COM_OPTS += -C $(AVRDUDE_CONF) 117 | AVRDUDE_ISP_OPTS = -c $(PROGRAMMER) -P usb 118 | 119 | # ------------------------------------------------------------------------------ 120 | # Main targets 121 | # ------------------------------------------------------------------------------ 122 | 123 | all: $(BUILD_DIR) $(TARGET_HEX) 124 | 125 | $(BUILD_DIR): 126 | mkdir -p $(BUILD_DIR) 127 | 128 | $(TARGET_ELF): $(OBJS) 129 | $(CC) $(LDFLAGS) -o $@ $(OBJS) $(SYS_OBJS) 130 | 131 | $(DEP_FILE): $(BUILD_DIR) $(DEPS) 132 | cat $(DEPS) > $(DEP_FILE) 133 | 134 | bin: $(TARGET_BIN) 135 | 136 | upload: $(TARGET_HEX) 137 | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) \ 138 | -U flash:w:$(TARGET_HEX):i -U lock:w:0x$(LOCK):m 139 | 140 | clean: 141 | $(REMOVE) $(OBJS) $(TARGETS) $(DEP_FILE) $(DEPS) 142 | 143 | depends: $(DEPS) 144 | cat $(DEPS) > $(DEP_FILE) 145 | 146 | $(TARGET).size: $(TARGET_ELF) 147 | $(SIZE) $(TARGET_ELF) > $(TARGET).size 148 | 149 | $(BUILD_DIR)$(TARGET).top_symbols: $(TARGET_ELF) 150 | $(NM) $(TARGET_ELF) --size-sort -C -f bsd -r > $@ 151 | 152 | size: $(TARGET).size 153 | cat $(TARGET).size | awk '{ print $$1+$$2 }' | tail -n1 154 | 155 | ramsize: $(TARGET).size 156 | cat $(TARGET).size | awk '{ print $$2+$$3 }' | tail -n1 157 | 158 | size_report: build/$(TARGET)/$(TARGET).lss build/$(TARGET)/$(TARGET).top_symbols 159 | 160 | .PHONY: all clean depends upload 161 | 162 | include $(DEP_FILE) 163 | 164 | # ------------------------------------------------------------------------------ 165 | # Set fuses 166 | # ------------------------------------------------------------------------------ 167 | 168 | terminal: 169 | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) -e -tuF 170 | 171 | fuses: 172 | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) -e -u \ 173 | -U efuse:w:0x$(EFUSE):m \ 174 | -U hfuse:w:0x$(HFUSE):m \ 175 | -U lfuse:w:0x$(LFUSE):m \ 176 | -U lock:w:0x$(LOCK):m 177 | 178 | # ------------------------------------------------------------------------------ 179 | # Program (fuses + firmware) a blank chip 180 | # ------------------------------------------------------------------------------ 181 | 182 | bootstrap: bake 183 | 184 | bake: $(FIRMWARE) 185 | echo "sck 10\nquit\n" | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) -e -tuF 186 | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) -e -u \ 187 | -U efuse:w:0x$(EFUSE):m \ 188 | -U hfuse:w:0x$(HFUSE):m \ 189 | -U lfuse:w:0x$(LFUSE):m \ 190 | -U lock:w:0x$(LOCK):m 191 | echo "sck 1\nquit\n" | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) -e -tuF 192 | $(AVRDUDE) $(AVRDUDE_COM_OPTS) $(AVRDUDE_ISP_OPTS) \ 193 | -U flash:w:$(TARGET_HEX):i -U lock:w:0x$(LOCK):m 194 | -------------------------------------------------------------------------------- /bootloader/bootloader.c: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Emilie Gillet. 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | // 14 | // ----------------------------------------------------------------------------- 15 | // 16 | // Bootloader supporting MIDI SysEx update. 17 | // 18 | // Caveat: assumes the firmware flashing is always done from first to last 19 | // block, in increasing order. Random access flashing is not supported! 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | uint16_t page = 0; 29 | uint8_t rx_buffer[SPM_PAGESIZE + 1]; 30 | 31 | void (*main_entry_point)(void) = 0x0000; 32 | 33 | inline void init() { 34 | cli(); 35 | // Insert here code for: 36 | // - Setting up the LEDs output port 37 | // - Setting up the switches input port 38 | } 39 | 40 | inline void write_status_leds(uint8_t pattern) { 41 | // Insert here code for outputing an 8 bits value to LEDs. 42 | } 43 | 44 | inline uint8_t bootloader_active() { 45 | // Insert here code for checking the combination of keys that initiate the 46 | // MIDI receive mode. 47 | return 1; 48 | } 49 | 50 | inline void write_buffer_to_flash() { 51 | uint16_t i; 52 | const uint8_t* p = rx_buffer; 53 | eeprom_busy_wait(); 54 | 55 | boot_page_erase(page); 56 | boot_spm_busy_wait(); 57 | 58 | for (i = 0; i < SPM_PAGESIZE; i += 2) { 59 | uint16_t w = *p++; 60 | w |= (*p++) << 8; 61 | boot_page_fill(page + i, w); 62 | } 63 | 64 | boot_page_write(page); 65 | boot_spm_busy_wait(); 66 | boot_rww_enable(); 67 | } 68 | 69 | static const uint8_t sysex_header[] = { 70 | 0xf0, // 71 | 0x00, 0x21, 0x02, // Manufacturer ID for Mutable instruments. 72 | 0x00, 0x7f, // Product ID for "any other project". 73 | }; 74 | 75 | enum SysExReceptionState { 76 | MATCHING_HEADER = 0, 77 | MATCHING_OLD_HEADER = 1, 78 | READING_COMMAND = 2, 79 | READING_DATA = 3, 80 | }; 81 | 82 | static const uint16_t kUartPrescaler = (F_CPU / (16L * 31250)) - 1; 83 | 84 | inline void midi_rx_loop() { 85 | uint8_t byte; 86 | uint16_t bytes_read = 0; 87 | uint16_t rx_buffer_index; 88 | uint8_t state = MATCHING_HEADER; 89 | uint8_t checksum; 90 | uint8_t sysex_commands[2]; 91 | uint8_t current_led = 1; 92 | uint8_t status = 0; 93 | uint8_t progress_counter = 0; 94 | 95 | UCSR0A &= ~_BV(U2X0); 96 | UBRR0H = kUartPrescaler >> 8; 97 | UBRR0L = kUartPrescaler; 98 | UCSR0B |= _BV(RXEN0); 99 | page = 0; 100 | write_status_leds(0x55); 101 | while (1) { 102 | while (!(UCSR0A & _BV(RXC0))); 103 | byte = UDR0; 104 | // In case we see a realtime message in the stream, safely ignore it. 105 | if (byte > 0xf0 && byte != 0xf7) { 106 | continue; 107 | } 108 | write_status_leds(status); 109 | switch (state) { 110 | case MATCHING_HEADER: 111 | if (byte == sysex_header[bytes_read]) { 112 | ++bytes_read; 113 | if (bytes_read == sizeof(sysex_header)) { 114 | bytes_read = 0; 115 | state = READING_COMMAND; 116 | } 117 | } else { 118 | bytes_read = 0; 119 | } 120 | break; 121 | 122 | case READING_COMMAND: 123 | if (byte < 0x80) { 124 | sysex_commands[bytes_read++] = byte; 125 | if (bytes_read == 2) { 126 | bytes_read = 0; 127 | rx_buffer_index = 0; 128 | checksum = 0; 129 | state = READING_DATA; 130 | } 131 | } else { 132 | state = MATCHING_HEADER; 133 | current_led = 1; 134 | status = 0; 135 | bytes_read = 0; 136 | } 137 | break; 138 | 139 | case READING_DATA: 140 | if (byte < 0x80) { 141 | if (bytes_read & 1) { 142 | rx_buffer[rx_buffer_index] |= byte & 0xf; 143 | if (rx_buffer_index < SPM_PAGESIZE) { 144 | checksum += rx_buffer[rx_buffer_index]; 145 | } 146 | ++rx_buffer_index; 147 | } else { 148 | rx_buffer[rx_buffer_index] = (byte << 4); 149 | } 150 | ++bytes_read; 151 | } else if (byte == 0xf7) { 152 | if (sysex_commands[0] == 0x7f && 153 | sysex_commands[1] == 0x00 && 154 | bytes_read == 0) { 155 | // Reset. 156 | return; 157 | } else if (rx_buffer_index == SPM_PAGESIZE + 1 && 158 | sysex_commands[0] == 0x7e && 159 | sysex_commands[1] == 0x00 && 160 | rx_buffer[rx_buffer_index - 1] == checksum) { 161 | // Block write. 162 | write_buffer_to_flash(); 163 | page += SPM_PAGESIZE; 164 | ++progress_counter; 165 | if (progress_counter == 32) { 166 | status |= current_led; 167 | current_led <<= 1; 168 | if (current_led == 0) { 169 | current_led = 1; 170 | status = 0; 171 | } 172 | progress_counter = 0; 173 | } 174 | status ^= current_led; 175 | } else { 176 | current_led = 1; 177 | status = 0; 178 | } 179 | state = MATCHING_HEADER; 180 | bytes_read = 0; 181 | } 182 | break; 183 | } 184 | } 185 | } 186 | 187 | int main(void) { 188 | uint8_t watchdog_status = MCUSR; 189 | MCUSR = 0; 190 | WDTCSR |= _BV(WDCE) | _BV(WDE); 191 | WDTCSR = 0; 192 | 193 | init(); 194 | if (bootloader_active()) { 195 | midi_rx_loop(); 196 | } 197 | main_entry_point(); 198 | } 199 | -------------------------------------------------------------------------------- /bootloader/makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2009 Emilie Gillet. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # This program is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | # GNU General Public License for more details. 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | # Setup for an ATMega644p with 1k of flash allocated to bootloader 15 | VERSION = 0.1 16 | MCU_NAME = 644 17 | TARGET = avr-midi-bootloader 18 | PACKAGES = bootloader 19 | EXTRA_DEFINES = -funsigned-char -fno-inline-small-functions -fmove-loop-invariants 20 | EXTRA_LD_FLAGS = ,--section-start=.text=0xfc00,--relax 21 | 22 | LOCK = 2f 23 | LFUSE = ff 24 | HFUSE = d6 25 | EFUSE = fd 26 | 27 | include base/makefile.mk 28 | 29 | include $(DEP_FILE) 30 | -------------------------------------------------------------------------------- /tools/README: -------------------------------------------------------------------------------- 1 | Python tools for manipulating .syx and .mid files for firmware updates, wavetables upload, etc. 2 | This code is released under a GPL3.0 license. 3 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/avr-midi-bootloader/baf39f16648a6bf865df3251d1bd66fcb0572fa9/tools/__init__.py -------------------------------------------------------------------------------- /tools/hex2sysex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/avr-midi-bootloader/baf39f16648a6bf865df3251d1bd66fcb0572fa9/tools/hex2sysex/__init__.py -------------------------------------------------------------------------------- /tools/hex2sysex/hex2sysex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2009 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # ----------------------------------------------------------------------------- 19 | # 20 | # Hex2SysEx utility 21 | 22 | """Hex2SysEx utility. 23 | 24 | usage: 25 | python hex2sysex.py \ 26 | [--page_size 128] \ 27 | [--delay 250] \ 28 | [--syx] \ 29 | [--output_file path_to/firmware.mid] \ 30 | path_to/firmware.hex 31 | """ 32 | 33 | import logging 34 | import optparse 35 | import os 36 | import struct 37 | import sys 38 | 39 | # Allows the code to be run from the project root directory 40 | sys.path.append('.') 41 | 42 | from tools.midi import midifile 43 | from tools.hexfile import hexfile 44 | 45 | 46 | def CreateMidifile( 47 | input_file_name, 48 | data, 49 | output_file, 50 | options): 51 | size = len(data) 52 | page_size = options.page_size 53 | delay = options.delay 54 | _, input_file_name = os.path.split(input_file_name) 55 | comments = [ 56 | 'Warning: contains OS data!', 57 | 'Created from %(input_file_name)s' % locals(), 58 | 'Size: %(size)d' % locals(), 59 | 'Page size: %(page_size)d' % locals(), 60 | 'Delay: %(delay)d ms' % locals()] 61 | m = midifile.Writer() 62 | if options.write_comments: 63 | for comment in comments: 64 | m.AddTrack().AddEvent(0, midifile.TextEvent(comment)) 65 | t = m.AddTrack() 66 | t.AddEvent(0, midifile.TempoEvent(120.0)) 67 | page_size *= 2 # Converts from words to bytes 68 | # The first SysEx block must not start at 0! Sequencers like Logic play the 69 | # first SysEx block everytime stop/play is pressed. 70 | time = 1 71 | syx_data = [] 72 | for i in xrange(0, size, page_size): 73 | block = ''.join(map(chr, data[i:i+page_size])) 74 | padding = page_size - len(block) 75 | block += '\x00' * padding 76 | mfr_id = options.manufacturer_id if not \ 77 | options.force_obsolete_manufacturer_id else '\x00\x20\x77' 78 | event = midifile.SysExEvent( 79 | mfr_id, 80 | struct.pack('>h', options.device_id), 81 | options.update_command + midifile.Nibblize(block)) 82 | t.AddEvent(time, event) 83 | syx_data.append(event.raw_message) 84 | # ms -> s -> beats -> ticks 85 | time += int(delay / 1000.0 / 0.5 * 96) 86 | event = midifile.SysExEvent( 87 | mfr_id, 88 | struct.pack('>h', options.device_id), 89 | options.reset_command) 90 | t.AddEvent(time, event) 91 | syx_data.append(event.raw_message) 92 | 93 | f = file(output_file, 'wb') 94 | if options.syx: 95 | f.write(''.join(syx_data)) 96 | else: 97 | m.Write(f, format=1) 98 | f.close() 99 | 100 | 101 | if __name__ == '__main__': 102 | parser = optparse.OptionParser() 103 | parser.add_option( 104 | '-p', 105 | '--page_size', 106 | dest='page_size', 107 | type='int', 108 | default=128, 109 | help='Flash page size in words') 110 | parser.add_option( 111 | '-d', 112 | '--delay', 113 | dest='delay', 114 | type='int', 115 | default=250, 116 | help='Delay between pages in milliseconds') 117 | parser.add_option( 118 | '-o', 119 | '--output_file', 120 | dest='output_file', 121 | default=None, 122 | help='Write output file to FILE', 123 | metavar='FILE') 124 | parser.add_option( 125 | '-m', 126 | '--manufacturer_id', 127 | dest='manufacturer_id', 128 | default='\x00\x21\x02', 129 | help='Manufacturer ID to use in SysEx message') 130 | parser.add_option( 131 | '-b', 132 | '--obsolete_manufacturer_id', 133 | dest='force_obsolete_manufacturer_id', 134 | default=False, 135 | action='store_true', 136 | help='Force the use of the manufacturer ID used in early products') 137 | parser.add_option( 138 | '-v', 139 | '--device_id', 140 | dest='device_id', 141 | type='int', 142 | default=2, 143 | help='Device ID to use in SysEx message') 144 | parser.add_option( 145 | '-u', 146 | '--update_command', 147 | dest='update_command', 148 | default='\x7e\x00', 149 | help='OS update SysEx command') 150 | parser.add_option( 151 | '-r', 152 | '--reset_command', 153 | dest='reset_command', 154 | default='\x7f\x00', 155 | help='Post-OS update reset SysEx command') 156 | parser.add_option( 157 | '-s', 158 | '--syx', 159 | dest='syx', 160 | action='store_true', 161 | default=False, 162 | help='Produces a .syx file instead of a MIDI file') 163 | parser.add_option( 164 | '-c', 165 | '--comments', 166 | dest='write_comments', 167 | action='store_true', 168 | default=False, 169 | help='Store additional technical gibberish') 170 | 171 | options, args = parser.parse_args() 172 | if len(args) != 1: 173 | logging.fatal('Specify one, and only one firmware .hex file!') 174 | sys.exit(1) 175 | 176 | data = hexfile.LoadHexFile(file(args[0])) 177 | if not data: 178 | logging.fatal('Error while loading .hex file') 179 | sys.exit(2) 180 | 181 | output_file = options.output_file 182 | if not output_file: 183 | if '.hex' in args[0]: 184 | output_file = args[0].replace('.hex', '.mid') 185 | else: 186 | output_file = args[0] + '.mid' 187 | 188 | CreateMidifile( 189 | args[0], 190 | data, 191 | output_file, 192 | options) 193 | -------------------------------------------------------------------------------- /tools/hexfile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/avr-midi-bootloader/baf39f16648a6bf865df3251d1bd66fcb0572fa9/tools/hexfile/__init__.py -------------------------------------------------------------------------------- /tools/hexfile/hexfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2009 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # ----------------------------------------------------------------------------- 19 | # 20 | # Python module for loading/writing Hex files. 21 | 22 | """Intel .hex file loader/writer""" 23 | 24 | import logging 25 | import sys 26 | 27 | 28 | def LoadHexFile(lines): 29 | """Loads a Hex file.""" 30 | base_address = None 31 | offset = 0 32 | data = [] 33 | for line_number, line in enumerate(lines): 34 | line = line.strip() 35 | if len(line) < 9: 36 | logging.info('Line %(line_number)d: line too short' % locals()) 37 | return None 38 | 39 | if not all(x in '0123456789abcdefABCDEF' for x in line[1:]): 40 | logging.info('Line %(line_number)d: unknown character' % locals()) 41 | return None 42 | 43 | bytes = [int(line[i:i+2], 16) for i in xrange(1, len(line), 2)] 44 | if bytes[0] != len(bytes) - 5: 45 | logging.info('Line %(line_number)d: invalid byte count' % locals()) 46 | return None 47 | 48 | if sum(bytes) % 256 != 0: 49 | logging.info('Line %(line_number)d: corrupted line' % locals()) 50 | return None 51 | 52 | if bytes[3] == 1: 53 | if bytes[0] != 0 or bytes[1] != 0 or bytes[2] != 0: 54 | logging.info('Line %(line_number)d: invalid end of file' % locals()) 55 | return None 56 | else: 57 | break 58 | elif bytes[3] == 0: 59 | address = offset << 16 | bytes[1] << 8 | bytes[2] 60 | padding_size = address + bytes[0] - len(data) 61 | if padding_size > 0: 62 | data += [0] * padding_size 63 | data[address:address + bytes[0]] = bytes[4:-1] 64 | elif bytes[3] == 4: 65 | address = bytes[4] << 8 | bytes[5] 66 | if base_address is None: 67 | base_address = address 68 | offset = 0 69 | else: 70 | offset = address - base_address 71 | return data 72 | 73 | 74 | def WriteHexFile(data, file_object, chunk_size=32): 75 | """Writes a Hex file.""" 76 | 77 | for address in xrange(0, len(data), chunk_size): 78 | chunk = data[address:address+chunk_size] 79 | chunk_len = len(chunk) 80 | address_l = address & 255 81 | address_h = address >> 8 82 | file_object.write(':%(chunk_len)02x%(address_h)02x%(address_l)02x00' % vars()) 83 | file_object.write(''.join('%02x' % value for value in chunk)) 84 | checksum = (-(chunk_len + address_l + address_h + sum(chunk))) & 255 85 | file_object.write('%02x\n' % checksum) 86 | file_object.write(':00000001FF\n') -------------------------------------------------------------------------------- /tools/midi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichenettes/avr-midi-bootloader/baf39f16648a6bf865df3251d1bd66fcb0572fa9/tools/midi/__init__.py -------------------------------------------------------------------------------- /tools/midi/midifile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | # 3 | # Copyright 2009 Emilie Gillet. 4 | # 5 | # Author: Emilie Gillet (emilie.o.gillet@gmail.com) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # ----------------------------------------------------------------------------- 19 | # 20 | # Midifile Writer and Reader. 21 | 22 | """Midifile writer. 23 | """ 24 | 25 | import bisect 26 | import math 27 | import struct 28 | 29 | 30 | def PackInteger(value, size=4): 31 | """Packs a python integer into a n-byte big endian byte sequence.""" 32 | return struct.pack('>%s' % {1: 'B', 2: 'H', 4: 'L'}[size], value) 33 | 34 | 35 | def UnpackInteger(value, size=4): 36 | """Packs a python integer into a n-byte big endian byte sequence.""" 37 | return struct.unpack('>%s' % {1: 'B', 2: 'H', 4: 'L'}[size], value)[0] 38 | 39 | 40 | def PackVariableLengthInteger(value): 41 | """Packs a python integer into a variable length byte sequence.""" 42 | if value == 0: 43 | return '\x00' 44 | s = value 45 | output = [] 46 | while value: 47 | to_write = value & 0x7f 48 | value = value >> 7 49 | output.insert(0, to_write) 50 | for i in xrange(len(output) - 1): 51 | output[i] |= 0x80 52 | output = ''.join(map(chr, output)) 53 | return output 54 | 55 | """Classes representing a MIDI event, with a Serialize method which encodes 56 | them into a string.""" 57 | 58 | class Event(object): 59 | def __init__(self): 60 | pass 61 | 62 | def Serialize(self, running_status): 63 | raise NotImplementedError 64 | 65 | 66 | class MetaEvent(Event): 67 | def __init__(self, id, data): 68 | assert len(data) < 256 69 | self.id = id 70 | self.data = data 71 | 72 | def Serialize(self, running_status): 73 | return ''.join([ 74 | '\xff', 75 | PackInteger(self.id, size=1), 76 | PackInteger(len(self.data), size=1), 77 | self.data]), None 78 | 79 | 80 | class TextEvent(MetaEvent): 81 | def __init__(self, text): 82 | self.text = text 83 | super(TextEvent, self).__init__(0x01, text) 84 | 85 | 86 | class CopyrightInfoEvent(MetaEvent): 87 | def __init__(self, text): 88 | self.text = text 89 | super(CopyrightInfoEvent, self).__init__(0x02, text) 90 | 91 | 92 | class TrackNameEvent(MetaEvent): 93 | def __init__(self, text): 94 | self.text = text 95 | super(TrackNameEvent, self).__init__(0x03, text) 96 | 97 | 98 | class TrackInstrumentNameEvent(MetaEvent): 99 | def __init__(self, text): 100 | self.text = text 101 | super(TrackInstrumentNameEvent, self).__init__(0x04, text) 102 | 103 | 104 | class LyricEvent(MetaEvent): 105 | def __init__(self, text): 106 | self.text = text 107 | super(LyricEvent, self).__init__(0x05, text) 108 | 109 | 110 | class MarkerEvent(MetaEvent): 111 | def __init__(self, text): 112 | self.text = text 113 | super(MarkerEvent, self).__init__(0x06, text) 114 | 115 | 116 | class CuePointEvent(MetaEvent): 117 | def __init__(self, text): 118 | self.text = text 119 | super(CuePointEvent, self).__init__(0x07, text) 120 | 121 | 122 | class EndOfTrackEvent(MetaEvent): 123 | def __init__(self): 124 | super(EndOfTrackEvent, self).__init__(0x2f, '') 125 | 126 | 127 | class TempoEvent(MetaEvent): 128 | def __init__(self, bpm): 129 | self.bpm = bpm 130 | value = 60000000.0 / bpm 131 | data = PackInteger(int(value), size=4)[1:] 132 | super(TempoEvent, self).__init__(0x51, data) 133 | 134 | 135 | class SMPTEOffsetEvent(MetaEvent): 136 | def __init__(self, h, m, s, f, sf): 137 | self.smpte_offset = (h, m, s, f, sf) 138 | data = ''.join(map(chr, [h, m, s, f, sf])) 139 | super(SMPTEOffsetEvent, self).__init__(0x54, data) 140 | 141 | 142 | class TimeSignatureEvent(MetaEvent): 143 | def __init__(self, numerator, denominator): 144 | self.numerator = numerator 145 | self.denominator = denominator 146 | data = ''.join([ 147 | PackInteger(numerator, size=1), 148 | PackInteger(int(math.log(denominator) / math.log(2)), size=1), 149 | '\x16\x08']) 150 | super(TimeSignatureEvent, self).__init__(0x58, data) 151 | 152 | 153 | class KeyEvent(MetaEvent): 154 | def __init__(self, sharp_flats, major_minor): 155 | self.sharp_flats = sharp_flats 156 | self.major_minor = major_minor 157 | data = ''.join([ 158 | PackInteger(sharp_flats, size=1), 159 | PackInteger(major_minor, size=1)]) 160 | super(KeyEvent, self).__init__(0x59, data) 161 | 162 | 163 | class BlobEvent(MetaEvent): 164 | def __init__(self, blob): 165 | self.data = blob 166 | super(BlobEvent, self).__init__(0x7f, blob) 167 | 168 | 169 | """Classic channel-oriented messages.""" 170 | 171 | class ChannelEvent(Event): 172 | def __init__(self, mask, channel, data): 173 | self.channel = channel 174 | self._status = mask | (channel - 1) 175 | self._data = PackInteger(self._status, size=1) + data 176 | 177 | def Serialize(self, running_status): 178 | if self._status == running_status: 179 | return self._data[1:], self._status 180 | else: 181 | return self._data, self._status 182 | 183 | 184 | class NoteOffEvent(ChannelEvent): 185 | def __init__(self, channel, note, velocity): 186 | data = PackInteger(note, size=1) + PackInteger(velocity, size=1) 187 | super(NoteOffEvent, self).__init__(0x80, channel, data) 188 | self.note = note 189 | self.velocity = velocity 190 | 191 | 192 | class NoteOnEvent(ChannelEvent): 193 | def __init__(self, channel, note, velocity): 194 | data = PackInteger(note, size=1) + PackInteger(velocity, size=1) 195 | super(NoteOnEvent, self).__init__(0x90, channel, data) 196 | self.note = note 197 | self.velocity = velocity 198 | 199 | 200 | class KeyAftertouchEvent(ChannelEvent): 201 | def __init__(self, channel, note, aftertouch): 202 | data = PackInteger(note, size=1) + PackInteger(aftertouch, size=1) 203 | super(KeyAftertouchEvent, self).__init__(0xa0, channel, data) 204 | self.note = note 205 | self.aftertouch = aftertouch 206 | 207 | 208 | class ControlChangeEvent(ChannelEvent): 209 | def __init__(self, channel, controller, value): 210 | data = PackInteger(controller, size=1) + PackInteger(value, size=1) 211 | super(ControlChangeEvent, self).__init__(0xb0, channel, data) 212 | self.controller = controller 213 | self.value = value 214 | 215 | 216 | class ProgramChangeEvent(ChannelEvent): 217 | def __init__(self, channel, program_number): 218 | data = PackInteger(program_number, size=1) 219 | super(ProgramChangeEvent, self).__init__(0xc0, channel, data) 220 | self.program_number = program_number 221 | 222 | 223 | class ChannelAftertouchEvent(ChannelEvent): 224 | def __init__(self, channel, aftertouch): 225 | data = PackInteger(aftertouch, size=1) 226 | super(ChannelAftertouchEvent, self).__init__(0xd0, channel, data) 227 | self.aftertouch = aftertouch 228 | 229 | 230 | class PitchBendEvent(ChannelEvent): 231 | def __init__(self, channel, pitch_bend_14_bits): 232 | data = PackInteger(pitch_bend_14_bits >> 7, size=1) + \ 233 | PackInteger(pitch_bend_14_bits & 0x7f, size=1) 234 | super(PitchBendEvent, self).__init__(0xe0, channel, data) 235 | self.pitch_bend = pitch_bend_14_bits 236 | 237 | 238 | class SystemEvent(Event): 239 | def __init__(self, id): 240 | self._id = id 241 | 242 | def Serialize(self, running_status): 243 | return PackInteger(self._id, size=1), running_status 244 | 245 | 246 | class ClockEvent(SystemEvent): 247 | def __init__(self): 248 | super(ClockEvent, self).__init__(0xf8) 249 | 250 | 251 | class StartEvent(SystemEvent): 252 | def __init__(self): 253 | super(StartEvent, self).__init__(0xfa) 254 | 255 | 256 | class ContinueEvent(SystemEvent): 257 | def __init__(self): 258 | super(ContinueEvent, self).__init__(0xfb) 259 | 260 | 261 | class StopEvent(SystemEvent): 262 | def __init__(self): 263 | super(StopEvent, self).__init__(0xfc) 264 | 265 | 266 | # TODO(pichenettes): also support pauses within a block transmission (F7) 267 | class SysExEvent(Event): 268 | def __init__(self, manufacturer_id, device_id, data): 269 | self.data = data 270 | self.message = ''.join([ 271 | manufacturer_id, 272 | device_id, 273 | data, 274 | '\xf7']) 275 | self.raw_message = '\xf0' + self.message 276 | assert all(ord(x) < 128 for x in self.message[:-1]) 277 | self.message = ''.join([ 278 | '\xf0', 279 | PackVariableLengthInteger(len(self.message)), 280 | self.message]) 281 | 282 | def Serialize(self, running_status): 283 | return self.message, None 284 | 285 | 286 | def Nibblize(data, add_checksum=True): 287 | """Converts a byte string into a nibble string. Also adds checksum""" 288 | output = [] 289 | if add_checksum: 290 | tail = [chr(sum(ord(char) for char in data) % 256)] 291 | else: 292 | tail = [] 293 | for char in map(ord, list(data) + tail): 294 | output.append(chr(char >> 4)) 295 | output.append(chr(char & 0x0f)) 296 | return ''.join(output) 297 | 298 | 299 | class Track(object): 300 | def __init__(self): 301 | self._events = [] 302 | 303 | def AddEvent(self, time, event): 304 | self._events.append((time, event)) 305 | 306 | def Sort(self): 307 | self._events = sorted(self._events) 308 | 309 | def Serialize(self): 310 | self.Sort() 311 | last_time, last_event = self._events[-1] 312 | if type(last_event) != EndOfTrackEvent: 313 | self._events.append((last_time + 1, EndOfTrackEvent())) 314 | data = [] 315 | current_time = 0 316 | running_status = None 317 | for time, event in self._events: 318 | delta = time - current_time 319 | data.append(PackVariableLengthInteger(delta)) 320 | event_data, running_status = event.Serialize(running_status) 321 | data.append(event_data) 322 | current_time = time 323 | return ''.join(data) 324 | 325 | def Write(self, file_object): 326 | file_object.write('MTrk') 327 | track_data = self.Serialize() 328 | file_object.write(PackInteger(len(track_data))) 329 | file_object.write(track_data) 330 | 331 | @property 332 | def events(self): 333 | return self._events 334 | 335 | 336 | class Writer(object): 337 | def __init__(self, ppq=96): 338 | self._tracks = [] 339 | self._ppq = ppq 340 | 341 | def AddTrack(self): 342 | new_track = Track() 343 | self._tracks.append(new_track) 344 | return new_track 345 | 346 | def _MergeTracks(self): 347 | new_track = Track() 348 | for track in self._tracks: 349 | for time_event in track.events: 350 | new_track.AddEvent(*time_event) 351 | new_track.Sort() 352 | return new_track 353 | 354 | def Write(self, file_object, format=0): 355 | tracks = self._tracks 356 | if format == 0: 357 | tracks = [self._MergeTracks()] 358 | 359 | # File header. 360 | file_object.write('MThd') 361 | file_object.write(PackInteger(6)) 362 | file_object.write(PackInteger(format, size=2)) 363 | if format == 0: 364 | file_object.write(PackInteger(1, size=2)) 365 | else: 366 | file_object.write(PackInteger(len(self._tracks), size=2)) 367 | file_object.write(PackInteger(self._ppq, size=2)) 368 | 369 | # Tracks. 370 | for track in tracks: 371 | track.Write(file_object) 372 | 373 | 374 | class Reader(object): 375 | def __init__(self): 376 | self.tracks = [] 377 | self.format = 0 378 | self.ppq = 96 379 | self._previous_status = 0 380 | 381 | def Read(self, f): 382 | assert f.read(4) == 'MThd' 383 | assert struct.unpack('>i', f.read(4))[0] == 6 384 | self.format = struct.unpack('>h', f.read(2))[0] 385 | assert self.format <= 2 386 | num_tracks = struct.unpack('>h', f.read(2))[0] 387 | self.ppq = struct.unpack('>h', f.read(2))[0] 388 | self._tempo_map = [] 389 | 390 | for i in xrange(num_tracks): 391 | self.tracks.append(self._ReadTrack(f)) 392 | self._CreateCumulativeTempoMap() 393 | 394 | 395 | def _ReadTrack(self, f): 396 | assert f.read(4) == 'MTrk' 397 | size = struct.unpack('>i', f.read(4))[0] 398 | t = 0 399 | events = [] 400 | while size > 0: 401 | delta_t, e, event_size = self._ReadEvent(f) 402 | t += delta_t 403 | if e: 404 | events.append((t, e)) 405 | if type(e) == TempoEvent: 406 | self._tempo_map.append((t, e.bpm)) 407 | size -= event_size 408 | return events 409 | 410 | def _CreateCumulativeTempoMap(self): 411 | t = 0.0 412 | current_tempo = 120.0 413 | previous_beat = 0 414 | cumulative_tempo_map = [(0, 0.0, current_tempo)] 415 | for beat, tempo in sorted(self._tempo_map): 416 | beats = float(beat - previous_beat) / self.ppq 417 | t += beats * 60.0 / current_tempo 418 | cumulative_tempo_map.append((beat, t, tempo)) 419 | current_tempo = tempo 420 | previous_beat = beat 421 | self._tempo_map = cumulative_tempo_map 422 | 423 | def AbsoluteTime(self, t): 424 | index = bisect.bisect_left(self._tempo_map, (t, 0, 0)) 425 | index = max(index - 1, 0) 426 | start_beat, start_seconds, tempo = self._tempo_map[index] 427 | return start_seconds + float(t - start_beat) / self.ppq * 60.0 / tempo 428 | 429 | def _ReadVariableLengthInteger(self, f): 430 | v = 0 431 | size = 0 432 | while True: 433 | v <<= 7 434 | byte = UnpackInteger(f.read(1), size=1) 435 | size += 1 436 | v |= (byte & 0x7f) 437 | if not (byte & 0x80): 438 | break 439 | return v, size 440 | 441 | def _ReadEvent(self, f): 442 | delta_t, size = self._ReadVariableLengthInteger(f) 443 | event_byte = ord(f.read(1)) 444 | size += 1 445 | if event_byte < 0x80: 446 | if self._previous_status: 447 | f.seek(f.tell() - 1) 448 | size -= 1 449 | event_byte = self._previous_status 450 | else: 451 | return delta_t, None, size 452 | 453 | event_type = event_byte & 0xf0 454 | channel = event_byte & 0xf 455 | channel += 1 456 | if event_type == 0x80: 457 | self._previous_status = event_type 458 | note = ord(f.read(1)) 459 | velo = ord(f.read(1)) 460 | event = NoteOffEvent(channel, note, velo) 461 | size += 2 462 | elif event_type == 0x90: 463 | self._previous_status = event_type 464 | event = NoteOnEvent(channel, ord(f.read(1)), ord(f.read(1))) 465 | size += 2 466 | elif event_type == 0xa0: 467 | self._previous_status = event_type 468 | event = KeyAftertouchEvent(channel, ord(f.read(1)), ord(f.read(1))) 469 | size += 2 470 | elif event_type == 0xb0: 471 | self._previous_status = event_type 472 | event = ControlChangeEvent(channel, ord(f.read(1)), ord(f.read(1))) 473 | size += 2 474 | elif event_type == 0xc0: 475 | self._previous_status = event_type 476 | event = ProgramChangeEvent(channel, ord(f.read(1))) 477 | size += 1 478 | elif event_type == 0xd0: 479 | self._previous_status = event_type 480 | event = ChannelAftertouchEvent(channel, ord(f.read(1))) 481 | size += 1 482 | elif event_type == 0xe0: 483 | self._previous_status = event_type 484 | event = PitchBendEvent(channel, (ord(f.read(1)) << 7) | ord(f.read(1))) 485 | size += 2 486 | elif event_byte == 0xff: 487 | event_type = ord(f.read(1)) 488 | size += 1 489 | event_size, event_size_size = self._ReadVariableLengthInteger(f) 490 | size += event_size_size 491 | bytes = f.read(event_size) 492 | size += event_size 493 | if event_type == 0x01: 494 | event = TextEvent(bytes) 495 | elif event_type == 0x02: 496 | event = CopyrightInfoEvent(bytes) 497 | elif event_type == 0x03: 498 | event = TrackNameEvent(bytes) 499 | elif event_type == 0x04: 500 | event = TrackInstrumentNameEvent(bytes) 501 | elif event_type == 0x05: 502 | event = LyricEvent(bytes) 503 | elif event_type == 0x06: 504 | event = MarkerEvent(bytes) 505 | elif event_type == 0x07: 506 | event = CuePointEvent(bytes) 507 | elif event_type == 0x20: 508 | current_channel = ord(bytes[0]) 509 | event = None 510 | elif event_type == 0x2f: 511 | event = EndOfTrackEvent() 512 | elif event_type == 0x51: 513 | value = UnpackInteger('\x00' + bytes, size=4) 514 | event = TempoEvent(60000000.0 / value) 515 | elif event_type == 0x54: 516 | event = SMPTEOffsetEvent(*map(ord, bytes)) 517 | elif event_type == 0x58: 518 | event = TimeSignatureEvent(ord(bytes[0]), 2 ** ord(bytes[1])) 519 | elif event_type == 0x59: 520 | event = KeyEvent(ord(bytes[0]), ord(bytes[1])) 521 | elif event_type == 0x7f: 522 | event = BlobEvent(bytes) 523 | elif event_byte == 0xf0: 524 | event_size, event_size_size = self._ReadVariableLengthInteger(f) 525 | size += event_size_size 526 | bytes = f.read(event_size) 527 | size += event_size 528 | event = SysExEvent(bytes[0:3], bytes[3:5], bytes[5:-1]) 529 | else: 530 | print event_byte, '!!' 531 | event = None 532 | return delta_t, event, size 533 | 534 | 535 | if __name__ == '__main__': 536 | m = Writer() 537 | t = m.AddTrack() 538 | t.AddEvent(0, TempoEvent(120.0)) 539 | 540 | t = m.AddTrack() 541 | t.AddEvent(1, SysExEvent( 542 | '\x00\x20\x77', 543 | '\x00\x01', 544 | '\x7f\x7f' + Nibblize('\xff\x00\xcc'))) 545 | 546 | f = file('output.mid', 'wb') 547 | m.Write(f, format=0) 548 | f.close() 549 | --------------------------------------------------------------------------------