1#!/usr/bin/python2.5 2# 3# Copyright 2009 Olivier Gillet. 4# 5# Author: Olivier Gillet (ol.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 <http://www.gnu.org/licenses/>. 17# 18# ----------------------------------------------------------------------------- 19# 20# Hex2SysEx utility 21 22"""Hex2SysEx utility. 23 24usage: 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 33import logging 34import optparse 35import os 36import struct 37import sys 38 39# Allows the code to be run from the project root directory 40sys.path.append('.') 41 42from tools.midi import midifile 43from tools.hexfile import hexfile 44 45 46def 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 101if __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 if args[0].endswith('.bin'): 177 data = map(ord, file(args[0], 'rb').read()) 178 else: 179 data = hexfile.LoadHexFile(file(args[0])) 180 if not data: 181 logging.fatal('Error while loading .hex file') 182 sys.exit(2) 183 184 output_file = options.output_file 185 if not output_file: 186 if '.hex' in args[0]: 187 output_file = args[0].replace('.hex', '.mid') 188 else: 189 output_file = args[0] + '.mid' 190 191 CreateMidifile( 192 args[0], 193 data, 194 output_file, 195 options) 196