Source code for jvconnected.interfaces.midi.bcf

#! /usr/bin/env python

from loguru import logger

import argparse
import asyncio
from typing import (
    List, Sequence, ByteString, ClassVar, Tuple, Dict, Optional, Union,
)

import mido

from jvconnected.interfaces.midi import bcf_sysex
from jvconnected.interfaces.midi.bcf_sysex import Preset, BCLBlock
from jvconnected.interfaces.midi.mapper import MidiMapper, Map


[docs]def build_preset(mapper: Optional[MidiMapper] = None) -> Preset: """Build a :class:`~.bcf_sysex.Preset` from the definitions in the given :class:`~.mapper.MidiMapper` Each of the :class:`~.mapper.Map` definitions will be assigned as encoders within an encoder group on the BCF. Since there are four encoder groups, this allows for control of up to four cameras, using the :attr:`midi channel <.bcf_sysex.ControlBase.channel>` to match the :attr:`~jvconnected.device.Device.device_index` of the camera(s). In addition, the map definition for "exposure.iris_pos" will be assigned to the first four faders. This allows iris control of four cameras from the faders. """ def build_control(pst, map_obj: Map, control_ix, cam_ix, **kwargs): enc_ix = cam_ix * 8 + control_ix btn_ix = cam_ix * 16 + control_ix encoder_disp_mode = kwargs.get('encoder_disp_mode', '1dot') if map_obj.map_type.startswith('controller'): enc_mode = ''.join(['absolute', map_obj.map_type.lstrip('controller')]) enc = pst.add_encoder( index=enc_ix, channel=cam_ix, mode=encoder_disp_mode, number=map_obj.controller, encoder_mode=enc_mode, value_default=0, ) elif map_obj.map_type == 'adjust_controller': # tx = mido.Message('control_change', control=spec['controller'], value=0) # tx_str = ''.join([f'${b:X}' for b in tx.bytes()[:2]]) # tx_str = f'{tx_str} ifp $7f ifn $00' pst.add_encoder( index=enc_ix, channel=cam_ix, mode=encoder_disp_mode, number=map_obj.controller, encoder_mode='relative-2', ) # pst.add_button( # index=btn_ix, channel=cam_ix, # number=spec['increment_note'], message_type='note', # ) # btn_ix += 8 # pst.add_button( # index=btn_ix, channel=cam_ix, # number=spec['decrement_note'], message_type='note', # ) else: print(f'no control built: control_ix={control_ix}, map_obj={map_obj}') if mapper is None: mapper = MidiMapper() pst = Preset(name='foo') iris_map = mapper['exposure.iris_pos'] tally_pgm = mapper['tally.program'] tally_pvw = mapper['tally.preview'] # iris_map = DEFAULT_MAPPING['exposure']['iris_pos'] # tally_pgm = DEFAULT_MAPPING['tally']['program'] # tally_pvw = DEFAULT_MAPPING['tally']['preview'] for cam_ix in range(4): # Iris mapped to faders 1-4 pst.add_fader( index=cam_ix+1, channel=cam_ix, number=iris_map.controller, mode='absolute/14', value_default=0 ) # Program tally on top button row, Preview on bottom pst.add_button( index=cam_ix+33, channel=cam_ix, value_max=100, message_type='note', number=tally_pgm.note, ) pst.add_button( index=cam_ix+41, channel=cam_ix, value_max=100, message_type='note', number=tally_pvw.note, ) control_ix = 1 # for grpkey, grp in DEFAULT_MAPPING.items(): for map_obj in mapper.iter_indexed(): if map_obj.group_name == 'tally': continue # for spkey, spec in grp.items(): if True: kw = {} if map_obj.name in ['red_normalized', 'blue_normalized', 'master_black_pos', 'detail_pos']: kw['encoder_disp_mode'] = 'pan' else: kw['encoder_disp_mode'] = 'bar' build_control(pst, map_obj, control_ix, cam_ix, **kw) control_ix += 1 return pst
def main(): p = argparse.ArgumentParser() p.add_argument('-p', '--port-name', dest='port_name') p.add_argument('--stdout', dest='stdout', action='store_true') p.add_argument('--store', dest='store', action='store_true') p.add_argument('-n', '--num', dest='num', type=int, default=1, help='Preset number (if --store is used)') args = p.parse_args() if args.stdout: args.port_name = '__stdout__' elif not args.port_name: all_io_ports = set(mido.get_ioport_names()) bcf_ports = [name for name in all_io_ports if 'BCF2000' in name] non_loop = [name for name in all_io_ports if 'through' not in name.lower()] if len(bcf_ports) == 1: args.port_name = bcf_ports[0] elif len(non_loop) == 1: args.port_name = non_loop[0] else: raise Exception(f'Could not find suitable port from "{all_io_ports}"') logger.info(f'Sending to {args.port_name}...') pst = build_preset() if args.stdout: def print_output(obj: Union[BCLBlock, Preset]): if isinstance(obj, Preset): obj = obj.build_bcl_block() for item in obj.build_sysex_items(): print(item.bcl_text) print_output(pst) if args.store: blk = pst.build_store_block(args.num) print_output(blk) else: asyncio.run(pst.send_to_port_name(args.port_name, args.store, args.num)) log_msg = f'Preset sent to {args.port_name}' if args.store: log_msg = f'{log_msg} and store as preset {args.num}' logger.success(log_msg) if __name__ == '__main__': main()