├── .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 |
--------------------------------------------------------------------------------