Home | History | Annotate | Line # | Download | only in dev
midictl.c revision 1.1.2.3
      1 /* $NetBSD: midictl.c,v 1.1.2.3 2006/06/10 22:32:27 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.3 2006/06/10 22:32:27 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 &= ~0x7f;
    209 		mc-> rpn |=  PN_SET | (0x7f & ctlval[1]);
    210 		mc->nrpn &= ~PN_SET;
    211 		return;
    212 	case MIDI_CTRL_RPN_MSB:
    213 		mc-> rpn &= ~0x7f<<7;
    214 		mc-> rpn |=  PN_SET | (0x7f & ctlval[1])<<7;
    215 		mc->nrpn &= ~PN_SET;
    216 		return;
    217 	case MIDI_CTRL_NRPN_LSB:
    218 		mc->nrpn &= ~0x7f;
    219 		mc->nrpn |=  PN_SET | (0x7f & ctlval[1]);
    220 		mc-> rpn &= ~PN_SET;
    221 		return;
    222 	case MIDI_CTRL_NRPN_MSB:
    223 		mc->nrpn &= ~0x7f<<7;
    224 		mc->nrpn |=  PN_SET | (0x7f & ctlval[1])<<7;
    225 		mc-> rpn &= ~PN_SET;
    226 		return;
    227 	case MIDI_CTRL_DATA_ENTRY_LSB:
    228 		islsb = 1;
    229 		goto whichparm;
    230 	case MIDI_CTRL_DATA_ENTRY_MSB:
    231 		islsb = 0;
    232 	whichparm:
    233 		if ( 0 == ( (mc->rpn ^ mc->nrpn) & PN_SET ) )
    234 			return; /* exactly one must be current */
    235 		if ( mc->rpn & PN_SET ) {
    236 			key = mc->rpn;
    237 			c = RPN;
    238 		} else {
    239 			key = mc->nrpn;
    240 			c = NRPN;
    241 		}
    242 		key &= 0x3fff;
    243 		if ( 0x3fff == key ) /* 'null' parm# to lock out changes */
    244 			return;
    245 		enter14(mc, chan, c, key, islsb, ctlval[1]);
    246 		return;
    247 	case MIDI_CTRL_RPN_INCREMENT: /* XXX for later - these are a PITA to */
    248 	case MIDI_CTRL_RPN_DECREMENT: /* get right - 'right' varies by param */
    249 			/* see http://www.midi.org/about-midi/rp18.shtml */
    250 		return;
    251 	}
    252 
    253 	/*
    254 	 * Channel mode, RPN, and NRPN operations have been ruled out.
    255 	 * This is an ordinary control change.
    256 	 */
    257 
    258 	key = ctlval[0];
    259 	c = classify(&key, &islsb);
    260 
    261 	switch ( c ) {
    262 	case CTL14:
    263 		enter14(mc, chan, c, key, islsb, ctlval[1]);
    264 		break;
    265 	case CTL7:
    266 		bkt = store_locate(mc->store, c, chan, key);
    267 		if ( !mc->accept_any_ctl_rpn ) {
    268 			if ( NULL == bkt )
    269 				break;
    270 			val = store_extract(bkt, c, chan, key);
    271 			if ( !(val&C7_SET) )
    272 				break;
    273 		}
    274 		store_update(mc->store, bkt, c, chan, key,
    275 		    C7_SET | (0x7f & ctlval[1]));
    276 		mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
    277 		break;
    278 	case CTL1:
    279 		bkt = store_locate(mc->store, c, chan, key);
    280 		if ( !mc->accept_any_ctl_rpn ) {
    281 			if ( NULL == bkt )
    282 				break;
    283 			val = store_extract(bkt, c, chan, key);
    284 			if ( !(val&C1_SET) )
    285 				break;
    286 		}
    287 		store_update(mc->store, bkt, c, chan, key,
    288 		    C1_SET | (ctlval[1]>63));
    289 		mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
    290 		break;
    291 	case RPN:
    292 	case NRPN:
    293 		break; /* won't see these - sop for gcc */
    294 	}
    295 }
    296 
    297 uint_fast16_t
    298 midictl_read(midictl *mc, uint_fast8_t chan, uint_fast8_t ctlr,
    299              uint_fast16_t dflt)
    300 {
    301 	bucket *bkt;
    302 	uint_fast16_t key, val;
    303 	class c;
    304 	_Bool islsb;
    305 
    306 	key = ctlr;
    307 	c = classify(&key, &islsb);
    308 	switch ( c ) {
    309 	case CTL1:
    310 		bkt = store_locate(mc->store, c, chan, key);
    311 		if ( NULL == bkt ||
    312 		    !(C1_SET&(val = store_extract(bkt, c, chan, key))) ) {
    313 			val = C1_SET | (dflt > 63);
    314 			store_update(mc->store, bkt, c, chan, key, val);
    315 		}
    316 		return (val & 1) ? 127 : 0;
    317 	case CTL7:
    318 		bkt = store_locate(mc->store, c, chan, key);
    319 		if ( NULL == bkt ||
    320 		    !(C7_SET&(val = store_extract(bkt, c, chan, key))) ) {
    321 			val = C7_SET | (dflt & 0x7f);
    322 			store_update(mc->store, bkt, c, chan, key, val);
    323 		}
    324 		return val & 0x7f;
    325 	case CTL14:
    326 		_MIDICTL_ASSERT(!islsb);
    327 		return read14(mc, chan, c, key, dflt);
    328 	case RPN:
    329 	case NRPN:
    330 		break; /* sop for gcc */
    331 	}
    332 	return 0; /* sop for gcc */
    333 }
    334 
    335 uint_fast16_t
    336 midictl_rpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
    337                  uint_fast16_t dflt)
    338 {
    339 	return read14(mc, chan, RPN, ctlr, dflt);
    340 }
    341 
    342 uint_fast16_t
    343 midictl_nrpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
    344                   uint_fast16_t dflt)
    345 {
    346 	return read14(mc, chan, NRPN, ctlr, dflt);
    347 }
    348 
    349 static void
    350 reset_all_controllers(midictl *mc, uint_fast8_t chan)
    351 {
    352 	uint_fast16_t ctlr, key;
    353 	class c;
    354 	_Bool islsb;
    355 	bucket *bkt;
    356 
    357 	for ( ctlr = 0 ; ; ++ ctlr ) {
    358 		switch ( ctlr ) {
    359 		/*
    360 		 * exempt by http://www.midi.org/about-midi/rp15.shtml:
    361 		 */
    362 		case MIDI_CTRL_BANK_SELECT_MSB:		/* 0 */
    363 		case MIDI_CTRL_CHANNEL_VOLUME_MSB:	/* 7 */
    364 		case MIDI_CTRL_PAN_MSB:			/* 10 */
    365 			continue;
    366 		case MIDI_CTRL_BANK_SELECT_LSB:		/* 32 */
    367 			ctlr += 31; /* skip all these LSBs anyway */
    368 			continue;
    369 		case MIDI_CTRL_SOUND_VARIATION:		/* 70 */
    370 			ctlr += 9; /* skip all Sound Controllers */
    371 			continue;
    372 		case MIDI_CTRL_EFFECT_DEPTH_1:		/* 91 */
    373 			goto loop_exit; /* nothing more gets reset */
    374 		/*
    375 		 * exempt for our own personal reasons:
    376 		 */
    377 		case MIDI_CTRL_DATA_ENTRY_MSB:		/* 6 */
    378 			continue; /* doesn't go to the store */
    379 		}
    380 
    381 		key = ctlr;
    382 		c = classify(&key, &islsb);
    383 
    384 		bkt = store_locate(mc->store, c, chan, key);
    385 		if ( NULL == bkt )
    386 			continue;
    387 		store_update(mc->store, bkt, c, chan, key, 0); /* no C*SET */
    388 	}
    389 loop_exit:
    390 	mc->notify(mc->cookie, MIDICTL_RESET, chan, 0);
    391 }
    392 
    393 static void
    394 enter14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
    395         _Bool islsb, uint8_t val)
    396 {
    397 	bucket *bkt;
    398 	uint16_t stval;
    399 
    400 	bkt = store_locate(mc->store, c, chan, key);
    401 	stval = (NULL == bkt) ? 0 : store_extract(bkt, c, chan, key);
    402 	if ( !(stval&(C14MSET|C14LSET)) ) {
    403 		if ( !((NRPN==c)? mc->accept_any_nrpn: mc->accept_any_ctl_rpn) )
    404 			return;
    405 	}
    406 	if ( islsb )
    407 		stval = C14LSET | val | ( stval & ~0x7f );
    408 	else
    409 		stval = C14MSET | ( val << 7 ) | ( stval & ~0x3f80 );
    410 	store_update(mc->store, bkt, c, chan, key, stval);
    411 	mc->notify(mc->cookie, CTL14 == c ? MIDICTL_CTLR
    412 		             : RPN   == c ? MIDICTL_RPN
    413 			     : MIDICTL_NRPN, chan, key);
    414 }
    415 
    416 static uint_fast16_t
    417 read14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
    418        uint_fast16_t dflt)
    419 {
    420 	bucket *bkt;
    421 	uint16_t val;
    422 
    423 	bkt = store_locate(mc->store, c, chan, key);
    424 	if ( NULL == bkt )
    425 		goto neitherset;
    426 
    427 	val = store_extract(bkt, c, chan, key);
    428 	switch ( val & (C14MSET|C14LSET) ) {
    429 	case C14MSET|C14LSET:
    430 		return val & 0x3fff;
    431 	case C14MSET:
    432 		val = C14LSET | (val & ~0x7f) | (dflt & 0x7f);
    433 		break;
    434 	case C14LSET:
    435 		val = C14MSET | (val & ~0x3f8) | (dflt & 0x3f8);
    436 		break;
    437 neitherset:
    438 	case 0:
    439 		val = C14MSET|C14LSET | (dflt & 0x3fff);
    440 	}
    441 	store_update(mc->store, bkt, c, chan, key, val);
    442 	return val & 0x3fff;
    443 }
    444 
    445 /*
    446  * Determine the controller class; ranges based on
    447  * http://www.midi.org/about-midi/table3.shtml dated 1995/1999/2002
    448  * and viewed 2 June 2006.
    449  */
    450 static class
    451 classify(uint_fast16_t *key, _Bool *islsb) {
    452 	if ( *key < 32 ) {
    453 		*islsb = 0;
    454 		return CTL14;
    455 	} else if ( *key < 64 ) {
    456 		*islsb = 1;
    457 		*key -= 32;
    458 		return CTL14;
    459 	} else if ( *key < 70 ) {
    460 		*key -= 64;
    461 		return CTL1;
    462 	}	  	/* 70-84 defined, 85-90 undef'd, 91-95 def'd */
    463 	return CTL7;	/* 96-101,120- handled above, 102-119 all undef'd */
    464 		  	/* treat them all as CTL7 */
    465 }
    466 
    467 static void
    468 notify_no_one(void *cookie, midictl_evt evt, uint_fast8_t chan, uint_fast16_t k)
    469 {
    470 }
    471 
    472 #undef PN_SET
    473 #undef C14MSET
    474 #undef C14LSET
    475 #undef C7_SET
    476 #undef C1_SET
    477 
    478 /*
    479  *   I M P L E M E N T A T I O N     O F     T H E     S T O R E :
    480  *
    481  * MIDI defines a metric plethora of possible controllers, registered
    482  * parameters, and nonregistered parameters: a bit more than 32k possible words
    483  * to store. The saving grace is that only a handful are likely to appear in
    484  * typical MIDI data, and only a handful are likely implemented by or
    485  * interesting to a typical client. So the store implementation needs to be
    486  * suited to a largish but quite sparse data set.
    487  *
    488  * For greatest efficiency, this could be implemented over the hash API.
    489  * For now, it is implemented over libprop, which is not a perfect fit,
    490  * but because that API does so much more than the hash API, this code
    491  * has to do less, and simplicity is worth something.
    492  *
    493  * prop_numbers are uintmax_t's, which are wider than anything we store, and
    494  * to reduce waste we want to fill them. The choice is to fill an entry
    495  * with values for the same controller across some consecutive channels
    496  * (rather than for consecutive controllers on a channel) because very few
    497  * controllers are likely to be used, but those that are will probably be used
    498  * on more than one channel.
    499  */
    500 
    501 #include <prop/proplib.h>
    502 #include <sys/malloc.h>
    503 
    504 #define KEYSTRSIZE 8
    505 static void tokeystr(char s[static KEYSTRSIZE],
    506                      class c, uint_fast8_t chan, uint_fast16_t key);
    507 
    508 static uint_fast8_t const packing[] = {
    509 	[CTL1 ] = 4*sizeof(uintmax_t)/sizeof(uint8_t),
    510 	[CTL7 ] =   sizeof(uintmax_t)/sizeof(uint8_t),
    511 	[CTL14] =   sizeof(uintmax_t)/sizeof(uint16_t),
    512 	[RPN  ] =   sizeof(uintmax_t)/sizeof(uint16_t),
    513 	[NRPN ] =   sizeof(uintmax_t)/sizeof(uint16_t)
    514 };
    515 
    516 struct bucket {
    517 	union {
    518 		uintmax_t val;
    519 		uint8_t   c7[sizeof(uintmax_t)/sizeof(uint8_t)];
    520 		uint16_t c14[sizeof(uintmax_t)/sizeof(uint16_t)];
    521 	} __packed un;
    522 	midictl_store *ms;
    523 };
    524 
    525 struct midictl_store {
    526 	prop_dictionary_t pd;
    527 	bucket bkt; /* assume any one client nonreentrant (for now?) */
    528 };
    529 
    530 static midictl_store *
    531 store_init(void)
    532 {
    533 	midictl_store *s;
    534 
    535 	s = _MIDICTL_MALLOC(sizeof *s, M_DEVBUF);
    536 	s->pd = prop_dictionary_create();
    537 	s->bkt.ms = s;
    538 	return s;
    539 }
    540 
    541 static void
    542 store_done(midictl_store *s)
    543 {
    544 	prop_object_release(s->pd);
    545 	_MIDICTL_FREE(s, M_DEVBUF);
    546 }
    547 
    548 static bucket *
    549 store_locate(midictl_store *s, class c, uint_fast8_t chan, uint_fast16_t key)
    550 {
    551 	char buf[8];
    552 	prop_number_t pn;
    553 
    554 	tokeystr(buf, c, chan, key);
    555 	pn = (prop_number_t)prop_dictionary_get(s->pd, buf);
    556 	if ( NULL == pn ) {
    557 		s->bkt.un.val = 0;
    558 		return NULL;
    559 	}
    560 	s->bkt.un.val = prop_number_integer_value(pn);
    561 	return &s->bkt;
    562 }
    563 
    564 static uint16_t
    565 store_extract(bucket *b, class c, uint_fast8_t chan, uint_fast16_t key)
    566 {
    567 	chan %= packing[c];
    568 	switch ( c ) {
    569 	case CTL1:
    570 		return 3 & (b->un.c7[chan/4]>>(chan%4)*2);
    571 	case CTL7:
    572 		return b->un.c7[chan];
    573 	case CTL14:
    574 	case RPN:
    575 	case NRPN:
    576 		break;
    577 	}
    578 	return b->un.c14[chan];
    579 }
    580 
    581 static void
    582 store_update(midictl_store *s, bucket *b, class c, uint_fast8_t chan,
    583 	     uint_fast16_t key, uint16_t value)
    584 {
    585 	uintmax_t orig;
    586 	char buf[KEYSTRSIZE];
    587 	prop_number_t pn;
    588 	boolean_t success;
    589 	uint_fast8_t ent;
    590 
    591 	if ( NULL == b ) {
    592 		b = &s->bkt;
    593 		orig = 0;
    594 	} else
    595 		orig = b->un.val;
    596 
    597 	ent = chan % packing[c];
    598 
    599 	switch ( c ) {
    600 	case CTL1:
    601 		b->un.c7[ent/4] &= ~(3<<(ent%4)*2);
    602 		b->un.c7[ent/4] |= (3&value)<<(ent%4)*2;
    603 		break;
    604 	case CTL7:
    605 		b->un.c7[ent] = value;
    606 		break;
    607 	case CTL14:
    608 	case RPN:
    609 	case NRPN:
    610 		b->un.c14[ent] = value;
    611 		break;
    612 	}
    613 
    614 	if ( orig == b->un.val )
    615 		return;
    616 
    617 	tokeystr(buf, c, chan, key);
    618 
    619 	if ( 0 == b->un.val )
    620 		prop_dictionary_remove(s->pd, buf);
    621 	else {
    622 		pn = prop_number_create_integer(b->un.val);
    623 		_MIDICTL_ASSERT(NULL != pn);
    624 		success = prop_dictionary_set(s->pd, buf, pn);
    625 		_MIDICTL_ASSERT(success);
    626 		prop_object_release(pn);
    627 	}
    628 }
    629 
    630 static void
    631 tokeystr(char s[static KEYSTRSIZE],
    632          class c, uint_fast8_t chan, uint_fast16_t key)
    633 {
    634 	snprintf(s, KEYSTRSIZE, "%x%x%x", c, chan/packing[c], key);
    635 }
    636 
    637 #if defined(_MIDICTL_MAIN)
    638 void
    639 dumpstore(void)
    640 {
    641 	char *s = prop_dictionary_externalize(mc.store->pd);
    642 	printf("%s", s);
    643 	free(s);
    644 }
    645 #endif
    646