Skip to content

Commit

Permalink
updated for 1.21
Browse files Browse the repository at this point in the history
  • Loading branch information
d-huck committed Jan 13, 2020
2 parents c5e6193 + 42cb79f commit 078a2d6
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 14 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ data/d_tones.syx
data/haunted_hearts.syx
data/rtfm.syx
doc/.ipynb_checkpoints/
*.pyc
*.pyc
/test2.py
/test.py
.env/
29 changes: 25 additions & 4 deletions libdigitone/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@

)

# NOTE: This dictionary lists the relative location of the bytes used rather than the absolute position of the bytes used for each parameter.
# The reason for this is the Sound class breaks each sysex message into it's relative parts. The data portion of each patch is from
# byte 0x29 to byte 0x14e. If translating this library to another language, please take note.

PARAM_LOOK = {
'algorithm': ['0x28'],
'c': ['0x2b'],
Expand Down Expand Up @@ -312,13 +316,30 @@

# LSB maps from 0 - 64. The FLAG_BIT doubles the LSB. MSB adds 128 to the total.

'b': [7, '0x29', '0x2f', '0x30']
'b': [7, '0x29', '0x2f', '0x30'],

#TODO: Verify and test these offset parameters

}
'c_offset': [6, '0xa8', '0xa9', '0xaa'],
'a_offset': [4, '0xa9', '0xab', '0xac'],
'b1_offset': [3, '0xa9', '0xad', '0xae'],
'b2_offset': [1, '0xa9', '0xaf', '0xb0'],

# portamento settings work
'portamento': ['0xa4'],
'portamento_time': ['0xa6'],
'portamento_type': ['0x10f'],
'portamento_slope': ['0x112'],
'portamento_amt': ['0x10e'],
'portamento_style': ['0x110'],
'portamento_gating': ['0x113']
}

SYSEX_BEGIN = b'f0'+b'00'+b'20'+b'3c'+b'0d'+b'00'+b'53'+b'01'+b'01'

# Tables for output
# Reference table for human readable output of a few of the parameters. This is mostly for testing purposes though the reference
# may come in handy.

# TODO: tables for multi-switches, LFO Mult, LFO_Mode

PARAM_C = [
Expand Down Expand Up @@ -821,4 +842,4 @@
b'43': "reverb_send",
b'44': "delay_send",
b'45': "chorus_send",
}
}
2 changes: 1 addition & 1 deletion libdigitone/sound.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, patch):
"""

# Check data type
if patch[int(0x150):int(0x152)] != [b'02', b'49']:
if patch[int(0x14f):int(0x151)] != [b'02', b'48']:
logging.debug(patch[int(0x150):int(0x152)])
raise TypeError("This is not the correct patch size! Data is probably corrupt")

Expand Down
16 changes: 8 additions & 8 deletions libdigitone/sysex.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def request(message, track=0):
while outport.closed:
pass

# logging.debug('Port is opened!')
logging.debug('Port is opened!')
if message == 'patch':

# TODO: Add this to constants.py
Expand Down Expand Up @@ -144,19 +144,19 @@ def listen():
while inport.closed:
pass

logging.debug('Port is opened!')
# logging.debug('Port is opened!')

try:
for msg in inport:
if msg.type == 'sysex':
msg = bytes(msg.hex(), 'utf-8').split()
yield msg
# except GeneratorExit:
# logging.debug('Waiting for Port to close...')
# inport.close()
# while not inport.closed:
# pass
# logging.debug('Port Closed. Exiting...')
except GeneratorExit:
# logging.debug('Waiting for Port to close...')
inport.close()
while not inport.closed:
pass
# logging.debug('Port Closed. Exiting...')

# TODO: Change this exception. It works fine for prototyping but needs
# a better way to exit for a library.
Expand Down
208 changes: 208 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!./.env/bin/python

import libdigitone as dt
import logging
import argparse
import time
import threading # imported for threading in monitor arguement


def setup():
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--listen', action='store_true', help='Turns on verbose output')
parser.add_argument('-m', '--monitor', action='store_true', help='Monitor for parameter changes')
parser.add_argument('-r', '--request', dest='request', action='store',
help='Send a request to the digitone. Requires REQUEST argument to specify request type')
parser.add_argument('-c', '--close', action='store_true')
parser.add_argument('-d', '--data', action='store_true')
parser.add_argument('-t', '--dtun', action='store_true', help='displays the dtun parameters in three columns')
parser.add_argument('-p', '--parameter', dest='parameter', help='follows a single parameter change')
global args
args = parser.parse_args()

logging.basicConfig(format='[ %(levelname)s ] :: > %(message)s', level=logging.INFO)

def listen():
for message in dt.listen():
patch = dt.Sound(message)
# logging.info('Prefix: {}'.format(patch.prefix))
# logging.info('Meta: {}'.format(patch.meta))
# logging.info('Message: {}'.format(patch.meta[1]))
# logging.info('Name: {}'.format(patch.name_to_string()))
# logging.info('Tags: {}'.format(patch.tag_list))
logging.info('Data: '
' {}'.format(patch.param('harm')))
# logging.info('EOM: {}\n'.format(patch.eom))
def main():
if args.listen:
try:
listen()
except KeyboardInterrupt:
dt.listen().throw(GeneratorExit)

elif args.request:
while True:
dt.request(args.request)
time.sleep(.01)

elif args.dtun:
dt.request('patch')
logging.debug('LISTENING FOR DTUN')
for message in dt.listen():
dt.request('patch')
patch = dt.Sound(message)
number = 653.34

if 'patch_old' not in locals():
patch_old = patch.data
else:
if patch.data != patch_old:
decimal = int(((99 / 127) * int(patch.data[int(0x25)], 16)))
msb = int(patch.data[int(0x2f)], 16)
if patch.data[int(0x30)] == b'08':
number = (((2 * msb) + 1) - 128) + (decimal / 100)
# if number < 0:
print('{}: {}\t{}: {}\t {}: {}\t\t {:.2f}'.format('0x29', patch.data[int(0x29)],
'0x2f', patch.data[int(0x2f)],
'0x30', patch.data[int(0x30)],
number))
else:
number = ((2 * msb) - 128) + (decimal / 100)
print('{}: {}\t{}: {}\t {}: {}\t\t {:.2f}'.format('0x29', patch.data[int(0x29)],
'0x2f', patch.data[int(0x2f)],
'0x30', patch.data[int(0x30)],
number))


patch_old = patch.data
time.sleep(.01)

# elif args.parameter:
# param = args.parameter

elif args.parameter:
param = args.parameter
if param in dt.PARAM_LOOK.keys():
logging.debug("Listening for {}".format(param))
dt.request('patch')
for message in dt.listen():
patch = dt.Sound(message)
if 'patch_old' not in locals():
patch_old = patch.data
else:
if patch.data != patch_old:

if len(dt.PARAM_LOOK[param]) == 1:
print('{}: {}'.format(param,
int(patch.data[int(dt.PARAM_LOOK[param][0], 16)], 16)))

elif len(dt.PARAM_LOOK[param]) == 3:
neg_bit = dt.PARAM_LOOK[param][0]
neg_byte = '{:08b}'.format(int(patch.data[int(dt.PARAM_LOOK[param][1], 16)], 16))
value = int(patch.data[int(dt.PARAM_LOOK[param][2], 16)], 16)

if neg_byte[neg_bit] == '0':
print(param + ': ', value)
else:
print(param + ': ', value - 128)

elif len(dt.PARAM_LOOK[param]) == 4:
# TODO: refactor code. Check LFO first, then B as they are the outliers.
# since harm and
flag_bit = dt.PARAM_LOOK[param][0]
flag_byte = '{:08b}'.format(int(patch.data[int(dt.PARAM_LOOK[param][1], 16)], 16))
msb_value = int(patch.data[int(dt.PARAM_LOOK[param][2], 16)], 16)
lsb_value = int(patch.data[int(dt.PARAM_LOOK[param][3], 16)], 16)
if param == 'harm':
msb_value = msb_value - 63
if flag_byte[flag_bit] == '0':
lsb_value = int((50 / 127) * lsb_value)
print('{:.2f}'.format(msb_value + (lsb_value / 100)))
else:
lsb_value = int((50/ 127) * lsb_value + 50)
print('{:.2f}'.format(msb_value + (lsb_value / 100)))
elif param == 'b':
# TODO: figure out how to represent the 'b' parameter in a meaningful way

# lsb_value = (64/127) * lsb_value
msb_value = msb_value * 128
if flag_byte[flag_bit] == '1':
lsb_value = int((lsb_value + 127) * (64/127))
else:
lsb_value = int((64/127) * lsb_value)
value = int(((msb_value) + lsb_value))
# print(lsb_value, msb_value, value)
print('{}, {}, {}, {}: {}'.format(flag_byte[-1], msb_value, lsb_value, value,
dt.PARAM_B[value]))


elif 'lfo' in param:
lsb_value = (100/127) * lsb_value
if flag_byte[flag_bit] == '0':
msb_value = (msb_value * 2) - 128
else:
msb_value = (msb_value * 2) - 127
print('{:.2f}'.format(msb_value + (lsb_value / 100)))

else:
if flag_byte[flag_bit] == '0':
lsb_value = int((50 / 127) * lsb_value)
print('{:.2f}'.format(msb_value + (lsb_value / 100)))
else:
lsb_value = int((50/ 127) * lsb_value + 50)
print('{:.2f}'.format(msb_value + (lsb_value / 100)))
else:
print('how the fuck did you even get here? None of the parameters should even have this'
'many byte locations. Check yo goddamn dictionary')
patch_old = patch.data
dt.request('patch')
time.sleep(0.05)
else:
logging.error("Nah, {} is not a valid parameter.".format(param))

dt.request('patch')

elif args.monitor:
logging.info('Monitoring...')
dt.request('patch')
for message in dt.listen():
dt.request('patch')
patch = dt.Sound(message)

if 'patch_old' not in locals():
patch_old = patch.data
else:
for i in range(len(patch.data)):
if patch.data[i] != patch_old[i]:
print('{}: {}'.format(hex(i), patch.data[i]))
patch_old = patch.data
time.sleep(0.01)

elif args.close:
dt.sysex.listen().throw(KeyboardInterrupt)

elif args.data:
parameters = dt.constants.PARAM_LOOK
for param in parameters.keys():
if len(parameters[param]) == 2:
print('{}: {}'.format(param, parameters[param]))

else:
sysex = dt.decode('data/factory.syx')
message = dt.parse(sysex)
for msg in message:
patch = dt.Sound(msg)
logging.info('Prefix: {}'.format(patch.prefix))
# logging.info('Meta: {}'.format(patch.meta))
# logging.info('Message: {}'.format(patch.meta[1]))
logging.info('Name: {}'.format(patch.name_to_string()))
logging.info('Tags: {}'.format(patch.tag_list))
# logging.info('DATA: {}'.format(patch.))
logging.info('Data: {}'.format(patch.param_to_dict))
# logging.info('B {}'.format(patch.param('b')))
# logging.info('EOM: {}\n'.format(patch.eom))


if __name__ == '__main__':
setup()
main()

0 comments on commit 078a2d6

Please sign in to comment.