Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import cStringIO
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template
from music.midi import midifile
TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), 'templates')
MESSAGE_NAMES = [
'note off',
'note on',
'poly aftertouch',
'control change',
'program change',
'afertouch',
'pitch bend',
'realtime'
]
ACTIONS = [
'Transform',
'Pass and transform',
'CCize',
'Pass and CCize',
'Filter out'
]
OPERATIONS = [
'No modification',
'Add',
'Subtract',
'Invert',
'Set to',
'Map to range',
'Set to random',
'Add random'
]
MIDIPAL_BLOCK_SIZE = 32
MUTABLE_INSTRUMENTS_MFR_ID = '\x00\x21\x02'
DEVICE_ID = '\x00\x03'
MIDIPAL_BASE_SCRATCH_ADDRESS = 896
def clip_7bits(x):
try:
x = int(x)
except Exception, e:
x = 0
return min(max(x, 0), 127)
def null(x):
return 0
def clip_8bits(x):
try:
x = int(x)
if x < 0:
x += 256
except Exception, e:
x = 0
return min(max(x, 0), 255)
def clip_4bits(x):
try:
x = int(x)
except Exception, e:
x = 0
return min(max(x, 0), 15)
CLIP_FUNCTIONS = [
(null, null),
(clip_8bits, null),
(clip_8bits, null),
(null, null),
(clip_7bits, clip_7bits),
(clip_7bits, clip_7bits),
(clip_7bits, clip_7bits),
(clip_8bits, clip_8bits)
]
class Modifier(object):
def __init__(self):
self._channel_bitmask = 0
self._message_type_bitmask = 0
self._ranges = [(0, 127), (0, 127)]
self._action = 0
self._channel_remap = False
self._channel_shift = 0
self._delay = 0
self._value_transformation = [(0, 0, 0), (0, 0, 0)]
self.enabled = False
def set_from_request(self, attributes):
for i in xrange(16):
if attributes('channel_%d' % i):
self._channel_bitmask += (1 << i)
for i in xrange(8):
if attributes('message_%d' % i):
self._message_type_bitmask += (1 << i)
self._ranges = [
(clip_7bits(attributes('value_a_min')),
clip_7bits(attributes('value_a_max'))),
(clip_7bits(attributes('value_b_min')),
clip_7bits(attributes('value_b_max')))]
self._action = int(attributes('action'))
self._channel_remap = attributes('channel_action') == '1'
if self._channel_remap:
self._channel_shift = clip_4bits(
clip_7bits(attributes('channel_shift')) - 1)
else:
self._channel_shift = clip_4bits(attributes('channel_shift'))
self._delay = 0
self._value_transformation = []
for i in xrange(2):
prefix = 'value_transformation_%d' % i
op = int(attributes(prefix))
fn1, fn2 = CLIP_FUNCTIONS[op]
if attributes(prefix + '_preserve_zero'):
op |= 0x80
if attributes(prefix + '_wrap'):
op |= 0x40
if attributes(prefix + '_swap'):
op |= 0x20
argument_1 = fn1(attributes(prefix + '_argument_1'))
argument_2 = fn2(attributes(prefix + '_argument_2'))
self._value_transformation.append((op, argument_1, argument_2))
def from_bytestream(self, bytes):
self._channel_bitmask = bytes[0] | (bytes[1] << 8)
self._message_type_bitmask = bytes[2]
self._ranges[0] = bytes[3], bytes[4]
self._ranges[1] = bytes[5], bytes[6]
self._channel_remap = (bytes[7] & 0x10) != 0
self._channel_shift = bytes[7] & 0xf
if self._channel_remap:
self._channel_shift += 1
self._action = 0
if bytes[7] & 0x20:
self._action |= 2
if bytes[7] & 0x40:
self._action |= 1
if bytes[7] & 0x80:
self._action = 4
self._value_transformation[0] = bytes[10], bytes[11], bytes[12]
self._value_transformation[1] = bytes[13], bytes[14], bytes[15]
def get_bytestream(self):
bytes = [0] * 16
bytes[0] = self._channel_bitmask & 255
bytes[1] = self._channel_bitmask >> 8
bytes[2] = self._message_type_bitmask
bytes[3], bytes[4] = self._ranges[0]
bytes[5], bytes[6] = self._ranges[1]
bytes[7] = self._channel_shift
if self._channel_remap:
bytes[7] |= 0x10
if self._action == 4:
bytes[7] |= 0x80
else:
if self._action & 1:
bytes[7] |= 0x40
if self._action & 2:
bytes[7] |= 0x20
bytes[8] = 0
bytes[9] = 0
bytes[10], bytes[11], bytes[12] = self._value_transformation[0]
bytes[13], bytes[14], bytes[15] = self._value_transformation[1]
return ''.join(map(chr, bytes))
def get_template_dict(self):
d = {}
channels = []
for i in xrange(16):
channels.append({
'index': i,
'text': str(i + 1) + ('<br/>' if (i % 4) == 3 else ''),
'checked': 'checked' if (1 << i) & self._channel_bitmask else ''
})
d['channels'] = channels
messages = []
for i in xrange(8):
messages.append({
'index': i,
'text': MESSAGE_NAMES[i],
'checked': 'checked' if (1 << i) & self._message_type_bitmask else ''
})
d['messages'] = messages
d['ranges'] = {
'value_a_min': self._ranges[0][0],
'value_a_max': self._ranges[0][1],
'value_b_min': self._ranges[1][0],
'value_b_max': self._ranges[1][1]
}
actions = []
for i in xrange(len(ACTIONS)):
actions.append({
'text': ACTIONS[i],
'index': i,
'selected': 'selected' if self._action == i else ''
})
d['actions'] = actions
d['remap_channel'] = 'selected' if self._channel_remap else ''
d['shift_channel'] = 'selected' if not self._channel_remap else ''
d['channel_shift'] = self._channel_shift
value_transformations = []
for i in xrange(2):
op = self._value_transformation[i][0]
ops = []
for j in xrange(len(OPERATIONS)):
ops.append({
'text': OPERATIONS[j],
'index': j,
'selected': 'selected' if (op & 0xf) == j else ''})
value_transformations.append({
'index': i,
'ops': ops,
'preserve_zero': 'checked' if op & 0x80 else '',
'wrap': 'checked' if op & 0x40 else '',
'swap': 'checked' if op & 0x20 else '',
'argument_1': self._value_transformation[i][1],
'argument_2': self._value_transformation[i][2]
})
d['value_transformations'] = value_transformations
return d
class RetrieveHandler(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/html'
template_path = os.path.join(TEMPLATE_PATH, 'retrieve.html')
self.response.out.write(template.render(template_path, {}))
class MainHandler(webapp.RequestHandler):
def show_edit_page(self, filters):
self.response.headers['Content-Type'] = 'text/html'
template_path = os.path.join(TEMPLATE_PATH, 'index.html')
filters_dicts = []
for i, f in enumerate(filters):
d = f.get_template_dict()
d['index'] = i
d['index_base_one'] = i + 1
d['checked'] = 'checked' if f.enabled else ''
filters_dicts.append(d)
template_data = {
'title': 'MIDIpal programmer',
'filters': filters_dicts,
'extra_body_tags': 'onLoad=hideControls();'
}
self.response.out.write(template.render(template_path, template_data))
def get(self):
filters = [Modifier() for i in xrange(4)]
filters[0].enabled = True
self.show_edit_page(filters)
def parse_sysex(self, data):
# Strip 0xf0 and 0xf7
blocks = [x.strip('\xf7') for x in data.split('\xf0') if x]
# Strip manufacturer id and 2 command bytes + 4 nibbles of address
# + 2 nibbles of checksum
data = ''.join([x[11:-2] for x in blocks])
# Renibblize
data = map(ord, data)
data = [x * 16 + y for (x, y) in zip(data[::2], data[1::2])]
modifiers = []
for i in xrange(4):
m = Modifier()
default_bytestream = m.get_bytestream()
filter_data, data = data[:16], data[16:]
m.enabled = ''.join(map(chr, filter_data)) != default_bytestream
m.from_bytestream(filter_data)
modifiers.append(m)
return modifiers
def convert_to_sysex(self, modifiers):
data = ''.join([m.get_bytestream() for m in modifiers])
syx_data = []
for i in xrange(0, len(data), MIDIPAL_BLOCK_SIZE):
address = MIDIPAL_BASE_SCRATCH_ADDRESS + i
block = data[i:i+MIDIPAL_BLOCK_SIZE]
block += '\x00' * (MIDIPAL_BLOCK_SIZE - len(block))
block = chr(address & 0xff) + chr(address >> 8) + block
event = midifile.SysExEvent(
MUTABLE_INSTRUMENTS_MFR_ID,
DEVICE_ID,
'\x01\x20' + midifile.Nibblize(block))
syx_data.append(event.raw_message)
# Add a comment to notify the MIDIpal that the block received in the
# scratch area has to be copied to the current custom program area.
event = midifile.SysExEvent(
MUTABLE_INSTRUMENTS_MFR_ID,
DEVICE_ID,
'\x73\x00' + midifile.Nibblize(''))
syx_data.append(event.raw_message)
f = cStringIO.StringIO()
f.write(''.join(syx_data))
data = f.getvalue()
f.close()
return data
def post(self):
if self.request.get('syx'):
self.show_edit_page(self.parse_sysex(self.request.get('syx')))
return
modifiers = []
for i in xrange(4):
m = Modifier()
prefix = 'filter_%d_' % i
if self.request.get(prefix + 'enabled'):
m.set_from_request(lambda x: self.request.get(prefix + x))
modifiers.append(m)
self.response.headers['Content-Type'] = 'application/octet-stream'
self.response.headers['Content-disposition'] = \
'attachment; filename=user_program.syx'
self.response.out.write(self.convert_to_sysex(modifiers))
def main():
application = webapp.WSGIApplication([('/', MainHandler), ('/retrieve', RetrieveHandler)], debug=True)
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
1dc58f345This file is pinned to the selected release. Download the original to open it in your design tool.