Home | History | Annotate | Line # | Download | only in dev
midictl.c revision 1.1.2.1
      1 /* $NetBSD: midictl.c,v 1.1.2.1 2006/06/06 21:33:16 chap Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2006 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Chapman Flack.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 #include <sys/cdefs.h>
     39 __KERNEL_RCSID(0, "$NetBSD: midictl.c,v 1.1.2.1 2006/06/06 21:33:16 chap Exp $");
     40 
     41 /*
     42  * See midictl.h for an overview of the purpose and use of this module.
     43  */
     44 
     45 #if defined(_KERNEL)
     46 #define _MIDICTL_ASSERT(x) KASSERT(x)
     47 #define _MIDICTL_MALLOC(s,t) malloc((s), (t), M_WAITOK)
     48 #define _MIDICTL_FREE(s,t) free((s), (t))
     49 #include <sys/systm.h>
     50 #include <sys/types.h>
     51 #else
     52 #include <assert.h>
     53 #include <stdio.h>
     54 #include <stdlib.h>
     55 #define _MIDICTL_ASSERT(x) assert(x)
     56 #define _MIDICTL_MALLOC(s,t) malloc((s))
     57 #define _MIDICTL_FREE(s,t) free((s))
     58 #endif
     59 
     60 #include "midictl.h"
     61 
     62 /*
     63  * The upper part of this file is MIDI-aware, and deals with things like
     64  * decoding MIDI Control Change messages, dealing with the ones that require
     65  * special handling as mode messages or parameter updates, and so on.
     66  *
     67  * It relies on a "store" layer (implemented in the lower part of this file)
     68  * that only must be able to stash away 2-, 8-, or 16-bit quantities (which
     69  * it may pack into larger units as it sees fit) and find them again given
     70  * a class, channel, and key (controller/parameter number).
     71  *
     72  * The MIDI controllers can have 1-, 7-, or 14-bit values; the parameters are
     73  * also 14-bit. The 14-bit values have to be set in two MIDI messages, 7 bits
     74  * at a time. The MIDI layer uses store-managed 2- or 8-bit slots for the
     75  * smaller types, and uses the free high bit to indicate that it has explicitly
     76  * set the value. (Because the store is allowed to pack things, it may 'find'
     77  * a zero entry for a value we never set, because it shares a word with a
     78  * different value that has been set. We know it is not a real value because
     79  * the high bit is clear.)
     80  *
     81  * The 14-bit values are handled similarly: 16-bit store slots are used to hold
     82  * them, with the two free high bits indicating independently whether the MSB
     83  * and the LSB have been explicitly set--as two separate MIDI messages are
     84  * required. If such a control is queried when only one half has been explicitly
     85  * set, the result is as if it had been set to the specified default value
     86  * before the explicit set.
     87  */
     88 typedef struct bucket bucket; /* the store layer completes this type */
     89 
     90 typedef enum { CTL1, CTL7, CTL14, RPN, NRPN } class;
     91 
     92 /*
     93  * assert(does_not_apply(KNFNamespaceArgumentAgainstNamesInPrototypes,
     94  *    PrototypesOfStaticFunctionsWithinNonIncludedFile));
     95  */
     96 static void reset_all_controllers(midictl *mc, uint_fast8_t chan);
     97 static void enter14(midictl *mc, uint_fast8_t chan, class c,
     98                     uint_fast16_t key, _Bool islsb, uint8_t val);
     99 static uint_fast16_t read14(midictl *mc, uint_fast8_t chan, class c,
    100                             uint_fast16_t key, uint_fast16_t dflt);
    101 static class classify(uint_fast16_t *key, _Bool *islsb);
    102 static midictl_notify notify_no_one;
    103 
    104 static midictl_store *store_init(void);
    105 static void store_done(midictl_store *s);
    106 static bucket *store_locate(midictl_store *s, class c,
    107                             uint_fast8_t chan, uint_fast16_t key);
    108 static uint16_t store_extract(bucket *b, class c,
    109                               uint_fast8_t chan, uint_fast16_t key);
    110 static void store_update(midictl_store *s, bucket *b, class c,
    111                          uint_fast8_t chan, uint_fast16_t key, uint16_t value);
    112 
    113 #define PN_SET 0x8000  /* a parameter number has been explicitly set */
    114 #define C14MSET 0x8000 /* MSB of a 14-bit val has been set */
    115 #define C14LSET 0x4000 /* LSB of a 14-bit val has been set */
    116 #define C7_SET 0x80    /* a 7-bit ctl has been set */
    117 #define C1_SET 2       /* a 1-bit ctl has been set */
    118 
    119 #if defined(_MIDICTL_MAIN)
    120 #define XS(s) [MIDICTL_##s]=#s
    121 char const * const evt_strings[] = {
    122 	XS(CTLR), XS(RPN), XS(NRPN), XS(RESET), XS(NOTES_OFF),
    123 	XS(SOUND_OFF), XS(LOCAL), XS(MODE)
    124 };
    125 #undef XS
    126 
    127 void
    128 dbgnotify(void *cookie, midictl_evt e, uint_fast8_t chan, uint_fast16_t key)
    129 {
    130 	printf("NFY %p %s chan %u #%u\n", cookie, evt_strings[e], chan, key);
    131 }
    132 
    133 midictl mc = {
    134 	.accept_any_ctl_rpn = 0,
    135 	.accept_any_nrpn = 0,
    136 	.base_channel = 16,
    137 	.cookie = NULL,
    138 	.notify = dbgnotify
    139 };
    140 
    141 int
    142 main(int argc, char **argv)
    143 {
    144 	int cnt, a, b, c;
    145 
    146 	midictl_open(&mc);
    147 	do {
    148 		cnt = scanf("%i %i %i", &a, &b, &c);
    149 		if ( 3 == cnt ) {
    150 			midictl_change(&mc, a, (uint8_t[]){b,c});
    151 		}
    152 	} while ( EOF != cnt );
    153 	midictl_close(&mc);
    154 	return 0;
    155 }
    156 #endif /* defined(_MIDICTL_MAIN) */
    157 
    158 void
    159 midictl_open(midictl *mc)
    160 {
    161 	if ( NULL == mc->notify )
    162 		mc->notify = notify_no_one;
    163 	mc->store = store_init();
    164 }
    165 
    166 void
    167 midictl_close(midictl *mc)
    168 {
    169 	store_done(mc->store);
    170 }
    171 
    172 void
    173 midictl_change(midictl *mc, uint_fast8_t chan, uint8_t *ctlval)
    174 {
    175 	class c;
    176 	uint_fast16_t key, val;
    177 	_Bool islsb;
    178 	bucket *bkt;
    179 
    180 	switch ( ctlval[0] ) {
    181 	/*
    182 	 * Channel mode messages:
    183 	 */
    184 	case MIDI_CTRL_OMNI_OFF:
    185 	case MIDI_CTRL_OMNI_ON:
    186 	case MIDI_CTRL_POLY_OFF:
    187 	case MIDI_CTRL_POLY_ON:
    188 		if ( chan != mc->base_channel )
    189 			return; /* ignored - not on base channel */
    190 		else
    191 			return; /* XXX ignored anyway - not implemented yet */
    192 	case MIDI_CTRL_NOTES_OFF:
    193 		mc->notify(mc->cookie, MIDICTL_NOTES_OFF, chan, 0);
    194 		return;
    195 	case MIDI_CTRL_LOCAL:
    196 		mc->notify(mc->cookie, MIDICTL_LOCAL, chan, ctlval[1]);
    197 		return;
    198 	case MIDI_CTRL_SOUND_OFF:
    199 		mc->notify(mc->cookie, MIDICTL_SOUND_OFF, chan, 0);
    200 		return;
    201 	case MIDI_CTRL_RESET:
    202 		reset_all_controllers(mc, chan);
    203 		return;
    204 	/*
    205 	 * Control changes to be handled specially:
    206 	 */
    207 	case MIDI_CTRL_RPN_LSB:
    208 		mc-> rpn |=  PN_SET | (0x7f & ctlval[1]);
    209 		mc->nrpn &= ~PN_SET;
    210 		return;
    211 	case MIDI_CTRL_RPN_MSB:
    212 		mc-> rpn |=  PN_SET | (0x7f & ctlval[1])<<7;
    213 		mc->nrpn &= ~PN_SET;
    214 		return;
    215 	case MIDI_CTRL_NRPN_LSB:
    216 		mc->nrpn |=  PN_SET | (0x7f & ctlval[1]);
    217 		mc-> rpn &= ~PN_SET;
    218 		return;
    219 	case MIDI_CTRL_NRPN_MSB:
    220 		mc->nrpn |=  PN_SET | (0x7f & ctlval[1])<<7;
    221 		mc-> rpn &= ~PN_SET;
    222 		return;
    223 	case MIDI_CTRL_DATA_ENTRY_LSB:
    224 		islsb = 1;
    225 		goto whichparm;
    226 	case MIDI_CTRL_DATA_ENTRY_MSB:
    227 		islsb = 0;
    228 	whichparm:
    229 		if ( 0 == ( (mc->rpn ^ mc->nrpn) & PN_SET ) )
    230 			return; /* exactly one must be current */
    231 		if ( mc->rpn & PN_SET ) {
    232 			key = mc->rpn;
    233 			c = RPN;
    234 		} else {
    235 			key = mc->nrpn;
    236 			c = NRPN;
    237 		}
    238 		key &= 0x3fff;
    239 		if ( 0x3fff == key ) /* 'null' parm# to lock out changes */
    240 			return;
    241 		enter14(mc, chan, c, key, islsb, ctlval[1]);
    242 		return;
    243 	case MIDI_CTRL_RPN_INCREMENT: /* XXX for later - these are a PITA to */
    244 	case MIDI_CTRL_RPN_DECREMENT: /* get right - 'right' varies by param */
    245 		return;
    246 	}
    247 
    248 	/*
    249 	 * Channel mode, RPN, and NRPN operations have been ruled out.
    250 	 * This is an ordinary control change.
    251 	 */
    252 
    253 	key = ctlval[0];
    254 	c = classify(&key, &islsb);
    255 
    256 	switch ( c ) {
    257 	case CTL14:
    258 		enter14(mc, chan, c, key, islsb, ctlval[1]);
    259 		break;
    260 	case CTL7:
    261 		bkt = store_locate(mc->store, c, chan, key);
    262 		if ( !mc->accept_any_ctl_rpn ) {
    263 			if ( NULL == bkt )
    264 				break;
    265 			val = store_extract(bkt, c, chan, key);
    266 			if ( !(val&C7_SET) )
    267 				break;
    268 		}
    269 		store_update(mc->store, bkt, c, chan, key,
    270 		    C7_SET | (0x7f & ctlval[1]));
    271 		mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
    272 		break;
    273 	case CTL1:
    274 		bkt = store_locate(mc->store, c, chan, key);
    275 		if ( !mc->accept_any_ctl_rpn ) {
    276 			if ( NULL == bkt )
    277 				break;
    278 			val = store_extract(bkt, c, chan, key);
    279 			if ( !(val&C1_SET) )
    280 				break;
    281 		}
    282 		store_update(mc->store, bkt, c, chan, key,
    283 		    C1_SET | (ctlval[1]>63));
    284 		mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
    285 		break;
    286 	case RPN:
    287 	case NRPN:
    288 		break; /* won't see these - sop for gcc */
    289 	}
    290 }
    291 
    292 uint_fast16_t
    293 midictl_read(midictl *mc, uint_fast8_t chan, uint_fast8_t ctlr,
    294              uint_fast16_t dflt)
    295 {
    296 	bucket *bkt;
    297 	uint_fast16_t key, val;
    298 	class c;
    299 	_Bool islsb;
    300 
    301 	key = ctlr;
    302 	c = classify(&key, &islsb);
    303 	switch ( c ) {
    304 	case CTL1:
    305 		bkt = store_locate(mc->store, c, chan, key);
    306 		if ( NULL == bkt ||
    307 		    !(C1_SET&(val = store_extract(bkt, c, chan, key))) ) {
    308 			val = C1_SET | (dflt > 63);
    309 			store_update(mc->store, bkt, c, chan, key, val);
    310 		}
    311 		return (val & 1) ? 127 : 0;
    312 	case CTL7:
    313 		bkt = store_locate(mc->store, c, chan, key);
    314 		if ( NULL == bkt ||
    315 		    !(C7_SET&(val = store_extract(bkt, c, chan, key))) ) {
    316 			val = C7_SET | (dflt & 0x7f);
    317 			store_update(mc->store, bkt, c, chan, key, val);
    318 		}
    319 		return val & 0x7f;
    320 	case CTL14:
    321 		_MIDICTL_ASSERT(!islsb);
    322 		return read14(mc, chan, c, key, dflt);
    323 	case RPN:
    324 	case NRPN:
    325 		break; /* sop for gcc */
    326 	}
    327 	return 0; /* sop for gcc */
    328 }
    329 
    330 uint_fast16_t
    331 midictl_rpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
    332                  uint_fast16_t dflt)
    333 {
    334 	return read14(mc, chan, RPN, ctlr, dflt);
    335 }
    336 
    337 uint_fast16_t
    338 midictl_nrpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
    339                   uint_fast16_t dflt)
    340 {
    341 	return read14(mc, chan, NRPN, ctlr, dflt);
    342 }
    343 
    344 static void
    345 reset_all_controllers(midictl *mc, uint_fast8_t chan)
    346 {
    347 	uint_fast16_t ctlr, key;
    348 	class c;
    349 	_Bool islsb;
    350 	bucket *bkt;
    351 
    352 	for ( ctlr = 0 ; ; ++ ctlr ) {
    353 		switch ( ctlr ) {
    354 		/*
    355 		 * exempt by http://www.midi.org/about-midi/rp15.shtml:
    356 		 */
    357 		case MIDI_CTRL_BANK_SELECT_MSB:		/* 0 */
    358 		case MIDI_CTRL_CHANNEL_VOLUME_MSB:	/* 7 */
    359 		case MIDI_CTRL_PAN_MSB:			/* 10 */
    360 			continue;
    361 		case MIDI_CTRL_BANK_SELECT_LSB:		/* 32 */
    362 			ctlr += 31; /* skip all these LSBs anyway */
    363 			continue;
    364 		case MIDI_CTRL_SOUND_VARIATION:		/* 70 */
    365 			ctlr += 9; /* skip all Sound Controllers */
    366 			continue;
    367 		case MIDI_CTRL_EFFECT_DEPTH_1:		/* 91 */
    368 			goto loop_exit; /* nothing more gets reset */
    369 		/*
    370 		 * exempt for our own personal reasons:
    371 		 */
    372 		case MIDI_CTRL_DATA_ENTRY_MSB:		/* 6 */
    373 			continue; /* doesn't go to the store */
    374 		}
    375 
    376 		key = ctlr;
    377 		c = classify(&key, &islsb);
    378 
    379 		bkt = store_locate(mc->store, c, chan, key);
    380 		if ( NULL == bkt )
    381 			continue;
    382 		store_update(mc->store, bkt, c, chan, key, 0); /* no C*SET */
    383 	}
    384 loop_exit:
    385 	mc->notify(mc->cookie, MIDICTL_RESET, chan, 0);
    386 }
    387 
    388 static void
    389 enter14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
    390         _Bool islsb, uint8_t val)
    391 {
    392 	bucket *bkt;
    393 	uint16_t stval;
    394 
    395 	bkt = store_locate(mc->store, c, chan, key);
    396 	stval = (NULL == bkt) ? 0 : store_extract(bkt, c, chan, key);
    397 	if ( !(stval&(C14MSET|C14LSET)) ) {
    398 		if ( !((NRPN==c)? mc->accept_any_nrpn: mc->accept_any_ctl_rpn) )
    399 			return;
    400 	}
    401 	if ( islsb )
    402 		stval = C14LSET | val | ( stval & ~0x7f );
    403 	else
    404 		stval = C14MSET | ( val << 7 ) | ( stval & ~0x3f80 );
    405 	store_update(mc->store, bkt, c, chan, key, stval);
    406 	mc->notify(mc->cookie, CTL14 == c ? MIDICTL_CTLR
    407 		             : RPN   == c ? MIDICTL_RPN
    408 			     : MIDICTL_NRPN, chan, key);
    409 }
    410 
    411 static uint_fast16_t
    412 read14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
    413        uint_fast16_t dflt)
    414 {
    415 	bucket *bkt;
    416 	uint16_t val;
    417 
    418 	bkt = store_locate(mc->store, c, chan, key);
    419 	if ( NULL == bkt )
    420 		goto neitherset;
    421 
    422 	val = store_extract(bkt, c, chan, key);
    423 	switch ( val & (C14MSET|C14LSET) ) {
    424 	case C14MSET|C14LSET:
    425 		return val & 0x3fff;
    426 	case C14MSET:
    427 		val = C14LSET | (val & ~0x7f) | (dflt & 0x7f);
    428 		break;
    429 	case C14LSET:
    430 		val = C14MSET | (val & ~0x3f8) | (dflt & 0x3f8);
    431 		break;
    432 neitherset:
    433 	case 0:
    434 		val = C14MSET|C14LSET | (dflt & 0x3fff);
    435 	}
    436 	store_update(mc->store, bkt, c, chan, key, val);
    437 	return val & 0x3fff;
    438 }
    439 
    440 /*
    441  * Determine the controller class; ranges based on
    442  * http://www.midi.org/about-midi/table3.shtml dated 1995/1999/2002
    443  * and viewed 2 June 2006.
    444  */
    445 static class
    446 classify(uint_fast16_t *key, _Bool *islsb) {
    447 	if ( *key < 32 ) {
    448 		*islsb = 0;
    449 		return CTL14;
    450 	} else if ( *key < 64 ) {
    451 		*islsb = 1;
    452 		*key -= 32;
    453 		return CTL14;
    454 	} else if ( *key < 70 ) {
    455 		*key -= 64;
    456 		return CTL1;
    457 	}	  	/* 70-84 defined, 85-90 undef'd, 91-95 def'd */
    458 	return CTL7;	/* 96-101,120- handled above, 102-119 all undef'd */
    459 		  	/* treat them all as CTL7 */
    460 }
    461 
    462 static void
    463 notify_no_one(void *cookie, midictl_evt evt, uint_fast8_t chan, uint_fast16_t k)
    464 {
    465 }
    466 
    467 #undef PN_SET
    468 #undef C14MSET
    469 #undef C14LSET
    470 #undef C7_SET
    471 #undef C1_SET
    472 
    473 /*
    474  *   I M P L E M E N T A T I O N     O F     T H E     S T O R E :
    475  *
    476  * MIDI defines a metric plethora of possible controllers, registered
    477  * parameters, and nonregistered parameters: a bit more than 32k possible words
    478  * to store. The saving grace is that only a handful are likely to appear in
    479  * typical MIDI data, and only a handful are likely implemented by or
    480  * interesting to a typical client. So the store implementation needs to be
    481  * suited to a largish but quite sparse data set.
    482  *
    483  * For greatest efficiency, this could be implemented over the hash API.
    484  * For now, it is implemented over libprop, which is not a perfect fit,
    485  * but because that API does so much more than the hash API, this code
    486  * has to do less, and simplicity is worth something.
    487  *
    488  * prop_numbers are uintmax_t's, which are wider than anything we store, and
    489  * to reduce waste we want to fill them. The choice is to fill an entry
    490  * with values for the same controller across some consecutive channels
    491  * (rather than for consecutive controllers on a channel) because very few
    492  * controllers are likely to be used, but those that are will probably be used
    493  * on more than one channel.
    494  */
    495 
    496 #include <prop/proplib.h>
    497 #include <sys/malloc.h>
    498 
    499 #define KEYSTRSIZE 8
    500 static void tokeystr(char s[static KEYSTRSIZE],
    501                      class c, uint_fast8_t chan, uint_fast16_t key);
    502 
    503 static uint_fast8_t const packing[] = {
    504 	[CTL1 ] = 4*sizeof(uintmax_t)/sizeof(uint8_t),
    505 	[CTL7 ] =   sizeof(uintmax_t)/sizeof(uint8_t),
    506 	[CTL14] =   sizeof(uintmax_t)/sizeof(uint16_t),
    507 	[RPN  ] =   sizeof(uintmax_t)/sizeof(uint16_t),
    508 	[NRPN ] =   sizeof(uintmax_t)/sizeof(uint16_t)
    509 };
    510 
    511 struct bucket {
    512 	union {
    513 		uintmax_t val;
    514 		uint8_t   c7[sizeof(uintmax_t)/sizeof(uint8_t)];
    515 		uint16_t c14[sizeof(uintmax_t)/sizeof(uint16_t)];
    516 	} __packed un;
    517 	midictl_store *ms;
    518 };
    519 
    520 struct midictl_store {
    521 	prop_dictionary_t pd;
    522 	bucket bkt; /* assume any one client nonreentrant (for now?) */
    523 };
    524 
    525 static midictl_store *
    526 store_init(void)
    527 {
    528 	midictl_store *s;
    529 
    530 	s = _MIDICTL_MALLOC(sizeof *s, M_DEVBUF);
    531 	s->pd = prop_dictionary_create();
    532 	s->bkt.ms = s;
    533 	return s;
    534 }
    535 
    536 static void
    537 store_done(midictl_store *s)
    538 {
    539 	prop_object_release(s->pd);
    540 	_MIDICTL_FREE(s, M_DEVBUF);
    541 }
    542 
    543 static bucket *
    544 store_locate(midictl_store *s, class c, uint_fast8_t chan, uint_fast16_t key)
    545 {
    546 	char buf[8];
    547 	prop_number_t pn;
    548 
    549 	tokeystr(buf, c, chan, key);
    550 	pn = (prop_number_t)prop_dictionary_get(s->pd, buf);
    551 	if ( NULL == pn ) {
    552 		s->bkt.un.val = 0;
    553 		return NULL;
    554 	}
    555 	s->bkt.un.val = prop_number_integer_value(pn);
    556 	return &s->bkt;
    557 }
    558 
    559 static uint16_t
    560 store_extract(bucket *b, class c, uint_fast8_t chan, uint_fast16_t key)
    561 {
    562 	chan %= packing[c];
    563 	switch ( c ) {
    564 	case CTL1:
    565 		return 3 & (b->un.c7[chan/4]>>(chan%4)*2);
    566 	case CTL7:
    567 		return b->un.c7[chan];
    568 	case CTL14:
    569 	case RPN:
    570 	case NRPN:
    571 		break;
    572 	}
    573 	return b->un.c14[chan];
    574 }
    575 
    576 static void
    577 store_update(midictl_store *s, bucket *b, class c, uint_fast8_t chan,
    578 	     uint_fast16_t key, uint16_t value)
    579 {
    580 	uintmax_t orig;
    581 	char buf[KEYSTRSIZE];
    582 	prop_number_t pn;
    583 	boolean_t success;
    584 	uint_fast8_t ent;
    585 
    586 	if ( NULL == b ) {
    587 		b = &s->bkt;
    588 		orig = 0;
    589 	} else
    590 		orig = b->un.val;
    591 
    592 	ent = chan % packing[c];
    593 
    594 	switch ( c ) {
    595 	case CTL1:
    596 		b->un.c7[ent/4] &= ~(3<<(ent%4)*2);
    597 		b->un.c7[ent/4] |= (3&value)<<(ent%4)*2;
    598 		break;
    599 	case CTL7:
    600 		b->un.c7[ent] = value;
    601 		break;
    602 	case CTL14:
    603 	case RPN:
    604 	case NRPN:
    605 		b->un.c14[ent] = value;
    606 		break;
    607 	}
    608 
    609 	if ( orig == b->un.val )
    610 		return;
    611 
    612 	tokeystr(buf, c, chan, key);
    613 
    614 	if ( 0 == b->un.val )
    615 		prop_dictionary_remove(s->pd, buf);
    616 	else {
    617 		pn = prop_number_create_integer(b->un.val);
    618 		_MIDICTL_ASSERT(NULL != pn);
    619 		success = prop_dictionary_set(s->pd, buf, pn);
    620 		_MIDICTL_ASSERT(success);
    621 		prop_object_release(pn);
    622 	}
    623 }
    624 
    625 static void
    626 tokeystr(char s[static KEYSTRSIZE],
    627          class c, uint_fast8_t chan, uint_fast16_t key)
    628 {
    629 	snprintf(s, KEYSTRSIZE, "%x%x%x", c, chan/packing[c], key);
    630 }
    631 
    632 #if defined(_MIDICTL_MAIN)
    633 void
    634 dumpstore(void)
    635 {
    636 	char *s = prop_dictionary_externalize(mc.store->pd);
    637 	printf("%s", s);
    638 	free(s);
    639 }
    640 #endif
    641