1 /* $OpenBSD: midi.c,v 1.2 1999/01/02 00:59:08 niklas Exp $ */ 2 /* $NetBSD: midi.c,v 1.10 1998/12/20 14:26:44 drochner Exp $ */ 3 4 /* 5 * Copyright (c) 1998 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Lennart Augustsson (augustss@netbsd.org). 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40 #include "midi.h" 41 #include "sequencer.h" 42 43 #include <sys/param.h> 44 #include <sys/ioctl.h> 45 #include <sys/fcntl.h> 46 #include <sys/vnode.h> 47 #include <sys/select.h> 48 #include <sys/poll.h> 49 #include <sys/malloc.h> 50 #include <sys/proc.h> 51 #include <sys/systm.h> 52 #include <sys/syslog.h> 53 #include <sys/kernel.h> 54 #include <sys/signalvar.h> 55 #include <sys/conf.h> 56 #include <sys/audioio.h> 57 #include <sys/midiio.h> 58 #include <sys/device.h> 59 60 #include <dev/audio_if.h> 61 #include <dev/midi_if.h> 62 #include <dev/midivar.h> 63 64 #if NMIDI > 0 65 66 #ifdef AUDIO_DEBUG 67 #define DPRINTF(x) if (mididebug) printf x 68 #define DPRINTFN(n,x) if (mididebug >= (n)) printf x 69 int mididebug = 0; 70 #else 71 #define DPRINTF(x) 72 #define DPRINTFN(n,x) 73 #endif 74 75 int midi_wait; 76 77 void midi_in __P((void *, int)); 78 void midi_out __P((void *)); 79 int midi_start_output __P((struct midi_softc *, int)); 80 int midi_sleep_timo __P((int *, char *, int)); 81 int midi_sleep __P((int *, char *)); 82 void midi_wakeup __P((int *)); 83 void midi_initbuf __P((struct midi_buffer *)); 84 void midi_timeout __P((void *)); 85 86 #define __BROKEN_INDIRECT_CONFIG /* XXX */ 87 #ifdef __BROKEN_INDIRECT_CONFIG 88 int midiprobe __P((struct device *, void *, void *)); 89 #else 90 int midiprobe __P((struct device *, struct cfdata *, void *)); 91 #endif 92 void midiattach __P((struct device *, struct device *, void *)); 93 94 struct cfattach midi_ca = { 95 sizeof(struct midi_softc), midiprobe, midiattach 96 }; 97 98 struct cfdriver midi_cd = { 99 NULL, "midi", DV_DULL 100 }; 101 102 #ifdef MIDI_SAVE 103 #define MIDI_SAVE_SIZE 100000 104 int midicnt; 105 struct { 106 int cnt; 107 u_char buf[MIDI_SAVE_SIZE]; 108 } midisave; 109 #define MIDI_GETSAVE _IOWR('m', 100, int) 110 111 #endif 112 113 int 114 midiprobe(parent, match, aux) 115 struct device *parent; 116 #ifdef __BROKEN_INDIRECT_CONFIG 117 void *match; 118 #else 119 struct cfdata *match; 120 #endif 121 void *aux; 122 { 123 struct audio_attach_args *sa = aux; 124 125 DPRINTFN(6,("midiprobe: type=%d sa=%p hw=%p\n", 126 sa->type, sa, sa->hwif)); 127 return ((sa->type == AUDIODEV_TYPE_MIDI) ? 1 : 0); 128 } 129 130 void 131 midiattach(parent, self, aux) 132 struct device *parent, *self; 133 void *aux; 134 { 135 struct midi_softc *sc = (void *)self; 136 struct audio_attach_args *sa = aux; 137 struct midi_hw_if *hwp = sa->hwif; 138 void *hdlp = sa->hdl; 139 140 DPRINTFN(6, ("MIDI attach\n")); 141 142 #ifdef DIAGNOSTIC 143 if (hwp == 0 || 144 hwp->open == 0 || 145 hwp->close == 0 || 146 hwp->output == 0 || 147 hwp->getinfo == 0) { 148 printf("midi: missing method\n"); 149 return; 150 } 151 #endif 152 sc->hw_if = hwp; 153 sc->hw_hdl = hdlp; 154 midi_attach(sc, parent); 155 } 156 157 void 158 midi_attach(sc, parent) 159 struct midi_softc *sc; 160 struct device *parent; 161 { 162 struct midi_info mi; 163 164 sc->isopen = 0; 165 166 midi_wait = MIDI_WAIT * hz / 1000000; 167 if (midi_wait == 0) 168 midi_wait = 1; 169 170 sc->sc_dev = parent; 171 sc->hw_if->getinfo(sc->hw_hdl, &mi); 172 sc->props = mi.props; 173 printf(": <%s>\n", mi.name); 174 } 175 176 int 177 midi_unit_count() 178 { 179 return (midi_cd.cd_ndevs); 180 } 181 182 void 183 midi_initbuf(mb) 184 struct midi_buffer *mb; 185 { 186 mb->used = 0; 187 mb->usedhigh = MIDI_BUFSIZE; 188 mb->end = mb->start + mb->usedhigh; 189 mb->inp = mb->outp = mb->start; 190 } 191 192 int 193 midi_sleep_timo(chan, label, timo) 194 int *chan; 195 char *label; 196 int timo; 197 { 198 int st; 199 200 if (!label) 201 label = "midi"; 202 203 DPRINTFN(5, ("midi_sleep_timo: %p %s %d\n", chan, label, timo)); 204 *chan = 1; 205 st = tsleep(chan, PWAIT | PCATCH, label, timo); 206 *chan = 0; 207 #ifdef MIDI_DEBUG 208 if (st != 0) 209 printf("midi_sleep: %d\n", st); 210 #endif 211 return (st); 212 } 213 214 int 215 midi_sleep(chan, label) 216 int *chan; 217 char *label; 218 { 219 return (midi_sleep_timo(chan, label, 0)); 220 } 221 222 void 223 midi_wakeup(chan) 224 int *chan; 225 { 226 if (*chan) { 227 DPRINTFN(5, ("midi_wakeup: %p\n", chan)); 228 wakeup(chan); 229 *chan = 0; 230 } 231 } 232 233 static int midi_lengths[] = { 2,2,2,2,1,1,2,0 }; 234 /* Number of bytes in a MIDI command */ 235 #define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7]) 236 237 void 238 midi_in(addr, data) 239 void *addr; 240 int data; 241 { 242 struct midi_softc *sc = addr; 243 struct midi_buffer *mb = &sc->inbuf; 244 int i; 245 246 if (!sc->isopen) 247 return; 248 if (data == MIDI_ACK) 249 return; 250 DPRINTFN(3, ("midi_in: %p 0x%02x\n", sc, data)); 251 if (!(sc->flags & FREAD)) 252 return; /* discard data if not reading */ 253 254 switch(sc->in_state) { 255 case MIDI_IN_START: 256 if (MIDI_IS_STATUS(data)) { 257 switch(data) { 258 case 0xf0: /* Sysex */ 259 sc->in_state = MIDI_IN_SYSEX; 260 break; 261 case 0xf1: /* MTC quarter frame */ 262 case 0xf3: /* Song select */ 263 sc->in_state = MIDI_IN_DATA; 264 sc->in_msg[0] = data; 265 sc->in_pos = 1; 266 sc->in_left = 1; 267 break; 268 case 0xf2: /* Song position pointer */ 269 sc->in_state = MIDI_IN_DATA; 270 sc->in_msg[0] = data; 271 sc->in_pos = 1; 272 sc->in_left = 2; 273 break; 274 default: 275 if (MIDI_IS_COMMON(data)) { 276 sc->in_msg[0] = data; 277 sc->in_pos = 1; 278 goto deliver; 279 } else { 280 sc->in_state = MIDI_IN_DATA; 281 sc->in_msg[0] = sc->in_status = data; 282 sc->in_pos = 1; 283 sc->in_left = 284 MIDI_LENGTH(sc->in_status); 285 } 286 break; 287 } 288 } else { 289 if (MIDI_IS_STATUS(sc->in_status)) { 290 sc->in_state = MIDI_IN_DATA; 291 sc->in_msg[0] = sc->in_status; 292 sc->in_msg[1] = data; 293 sc->in_pos = 2; 294 sc->in_left = MIDI_LENGTH(sc->in_status) - 1; 295 } 296 } 297 return; 298 case MIDI_IN_DATA: 299 sc->in_msg[sc->in_pos++] = data; 300 if (--sc->in_left <= 0) 301 break; /* deliver data */ 302 return; 303 case MIDI_IN_SYSEX: 304 if (data == MIDI_SYSEX_END) 305 sc->in_state = MIDI_IN_START; 306 return; 307 } 308 deliver: 309 sc->in_state = MIDI_IN_START; 310 #if NSEQUENCER > 0 311 if (sc->seqopen) { 312 extern void midiseq_in __P((struct midi_dev *,u_char *,int)); 313 midiseq_in(sc->seq_md, sc->in_msg, sc->in_pos); 314 return; 315 } 316 #endif 317 318 if (mb->used + sc->in_pos > mb->usedhigh) { 319 DPRINTF(("midi_in: buffer full, discard data=0x%02x\n", 320 sc->in_msg[0])); 321 return; 322 } 323 for (i = 0; i < sc->in_pos; i++) { 324 *mb->inp++ = sc->in_msg[i]; 325 if (mb->inp >= mb->end) 326 mb->inp = mb->start; 327 mb->used++; 328 } 329 midi_wakeup(&sc->rchan); 330 selwakeup(&sc->rsel); 331 if (sc->async) 332 psignal(sc->async, SIGIO); 333 } 334 335 void 336 midi_out(addr) 337 void *addr; 338 { 339 struct midi_softc *sc = addr; 340 341 if (!sc->isopen) 342 return; 343 DPRINTFN(3, ("midi_out: %p\n", sc)); 344 midi_start_output(sc, 1); 345 } 346 347 int 348 midiopen(dev, flags, ifmt, p) 349 dev_t dev; 350 int flags, ifmt; 351 struct proc *p; 352 { 353 int unit = MIDIUNIT(dev); 354 struct midi_softc *sc; 355 struct midi_hw_if *hw; 356 int error; 357 358 if (unit >= midi_cd.cd_ndevs || 359 (sc = midi_cd.cd_devs[unit]) == NULL) 360 return (ENXIO); 361 DPRINTF(("midiopen %p\n", sc)); 362 363 hw = sc->hw_if; 364 if (!hw) 365 return (ENXIO); 366 if (sc->isopen) 367 return (EBUSY); 368 sc->in_state = MIDI_IN_START; 369 sc->in_status = 0; 370 error = hw->open(sc->hw_hdl, flags, midi_in, midi_out, sc); 371 if (error) 372 return (error); 373 sc->isopen++; 374 midi_initbuf(&sc->outbuf); 375 midi_initbuf(&sc->inbuf); 376 sc->flags = flags; 377 sc->rchan = 0; 378 sc->wchan = 0; 379 sc->pbus = 0; 380 sc->async = 0; 381 382 #ifdef MIDI_SAVE 383 if (midicnt != 0) { 384 midisave.cnt = midicnt; 385 midicnt = 0; 386 } 387 #endif 388 389 return (0); 390 } 391 392 int 393 midiclose(dev, flags, ifmt, p) 394 dev_t dev; 395 int flags, ifmt; 396 struct proc *p; 397 { 398 int unit = MIDIUNIT(dev); 399 struct midi_softc *sc = midi_cd.cd_devs[unit]; 400 struct midi_hw_if *hw = sc->hw_if; 401 int s, error; 402 403 DPRINTF(("midiclose %p\n", sc)); 404 405 midi_start_output(sc, 0); 406 error = 0; 407 s = splaudio(); 408 while (sc->outbuf.used > 0 && !error) { 409 DPRINTFN(2,("midiclose sleep used=%d\n", sc->outbuf.used)); 410 error = midi_sleep_timo(&sc->wchan, "mid_dr", 30*hz); 411 } 412 splx(s); 413 sc->isopen = 0; 414 hw->close(sc->hw_hdl); 415 #if NSEQUENCER > 0 416 sc->seqopen = 0; 417 sc->seq_md = 0; 418 #endif 419 return (0); 420 } 421 422 int 423 midiread(dev, uio, ioflag) 424 dev_t dev; 425 struct uio *uio; 426 int ioflag; 427 { 428 int unit = MIDIUNIT(dev); 429 struct midi_softc *sc = midi_cd.cd_devs[unit]; 430 struct midi_buffer *mb = &sc->inbuf; 431 int error; 432 u_char *outp; 433 int used, cc, n, resid; 434 int s; 435 436 DPRINTF(("midiread: %p, count=%d\n", sc, uio->uio_resid)); 437 438 error = 0; 439 resid = uio->uio_resid; 440 while (uio->uio_resid == resid && !error) { 441 s = splaudio(); 442 while (mb->used <= 0) { 443 if (ioflag & IO_NDELAY) { 444 splx(s); 445 return (EWOULDBLOCK); 446 } 447 error = midi_sleep(&sc->rchan, "mid rd"); 448 if (error) { 449 splx(s); 450 return (error); 451 } 452 } 453 used = mb->used; 454 outp = mb->outp; 455 splx(s); 456 cc = used; /* maximum to read */ 457 n = mb->end - outp; 458 if (n < cc) 459 cc = n; /* don't read beyond end of buffer */ 460 if (uio->uio_resid < cc) 461 cc = uio->uio_resid; /* and no more than we want */ 462 DPRINTFN(3, ("midiread: uiomove cc=%d\n", cc)); 463 error = uiomove(outp, cc, uio); 464 if (error) 465 break; 466 used -= cc; 467 outp += cc; 468 if (outp >= mb->end) 469 outp = mb->start; 470 s = splaudio(); 471 mb->outp = outp; 472 mb->used = used; 473 splx(s); 474 } 475 return (error); 476 } 477 478 void 479 midi_timeout(arg) 480 void *arg; 481 { 482 struct midi_softc *sc = arg; 483 484 DPRINTFN(3,("midi_timeout: %p\n", sc)); 485 midi_start_output(sc, 1); 486 } 487 488 int 489 midi_start_output(sc, intr) 490 struct midi_softc *sc; 491 int intr; 492 { 493 struct midi_buffer *mb = &sc->outbuf; 494 u_char *outp; 495 int error; 496 int s; 497 int i, mmax; 498 499 error = 0; 500 mmax = sc->props & MIDI_PROP_OUT_INTR ? 1 : MIDI_MAX_WRITE; 501 s = splaudio(); 502 if (sc->pbus && !intr) { 503 DPRINTFN(4, ("midi_start_output: busy\n")); 504 splx(s); 505 return (0); 506 } 507 sc->pbus = 1; 508 for (i = 0; i < mmax && mb->used > 0 && !error; i++) { 509 outp = mb->outp; 510 splx(s); 511 DPRINTFN(4, ("midi_start_output: %p i=%d, data=0x%02x\n", 512 sc, i, *outp)); 513 #ifdef MIDI_SAVE 514 midisave.buf[midicnt] = *outp; 515 midicnt = (midicnt + 1) % MIDI_SAVE_SIZE; 516 #endif 517 error = sc->hw_if->output(sc->hw_hdl, *outp++); 518 if (outp >= mb->end) 519 outp = mb->start; 520 s = splaudio(); 521 mb->outp = outp; 522 mb->used--; 523 } 524 midi_wakeup(&sc->wchan); 525 selwakeup(&sc->wsel); 526 if (sc->async) 527 psignal(sc->async, SIGIO); 528 if (mb->used > 0) { 529 if (!(sc->props & MIDI_PROP_OUT_INTR)) 530 timeout(midi_timeout, sc, midi_wait); 531 } else 532 sc->pbus = 0; 533 splx(s); 534 return (error); 535 } 536 537 int 538 midiwrite(dev, uio, ioflag) 539 dev_t dev; 540 struct uio *uio; 541 int ioflag; 542 { 543 int unit = MIDIUNIT(dev); 544 struct midi_softc *sc = midi_cd.cd_devs[unit]; 545 struct midi_buffer *mb = &sc->outbuf; 546 int error; 547 u_char *inp; 548 int used, cc, n; 549 int s; 550 551 DPRINTFN(2, ("midiwrite: %p, unit=%d, count=%d\n", sc, unit, 552 uio->uio_resid)); 553 554 error = 0; 555 while (uio->uio_resid > 0 && !error) { 556 s = splaudio(); 557 if (mb->used >= mb->usedhigh) { 558 DPRINTFN(3,("midi_write: sleep used=%d hiwat=%d\n", 559 mb->used, mb->usedhigh)); 560 if (ioflag & IO_NDELAY) { 561 splx(s); 562 return (EWOULDBLOCK); 563 } 564 error = midi_sleep(&sc->wchan, "mid wr"); 565 if (error) { 566 splx(s); 567 return (error); 568 } 569 } 570 used = mb->used; 571 inp = mb->inp; 572 splx(s); 573 cc = mb->usedhigh - used; /* maximum to write */ 574 n = mb->end - inp; 575 if (n < cc) 576 cc = n; /* don't write beyond end of buffer */ 577 if (uio->uio_resid < cc) 578 cc = uio->uio_resid; /* and no more than we have */ 579 error = uiomove(inp, cc, uio); 580 #ifdef MIDI_DEBUG 581 if (error) 582 printf("midi_write:(1) uiomove failed %d; " 583 "cc=%d inp=%p\n", 584 error, cc, inp); 585 #endif 586 if (error) 587 break; 588 inp = mb->inp + cc; 589 if (inp >= mb->end) 590 inp = mb->start; 591 s = splaudio(); 592 mb->inp = inp; 593 mb->used += cc; 594 splx(s); 595 error = midi_start_output(sc, 0); 596 } 597 return (error); 598 } 599 600 /* 601 * This write routine is only called from sequencer code and expects 602 * a write that is smaller than the MIDI buffer. 603 */ 604 int 605 midi_writebytes(unit, buf, cc) 606 int unit; 607 u_char *buf; 608 int cc; 609 { 610 struct midi_softc *sc = midi_cd.cd_devs[unit]; 611 struct midi_buffer *mb = &sc->outbuf; 612 int n, s; 613 614 DPRINTFN(2, ("midi_writebytes: %p, unit=%d, cc=%d\n", sc, unit, cc)); 615 DPRINTFN(3, ("midi_writebytes: %x %x %x\n",buf[0],buf[1],buf[2])); 616 617 s = splaudio(); 618 if (mb->used + cc >= mb->usedhigh) { 619 splx(s); 620 return (EWOULDBLOCK); 621 } 622 n = mb->end - mb->inp; 623 if (cc < n) 624 n = cc; 625 mb->used += cc; 626 bcopy(buf, mb->inp, n); 627 mb->inp += n; 628 if (mb->inp >= mb->end) { 629 mb->inp = mb->start; 630 cc -= n; 631 if (cc > 0) { 632 bcopy(buf + n, mb->inp, cc); 633 mb->inp += cc; 634 } 635 } 636 splx(s); 637 return (midi_start_output(sc, 0)); 638 } 639 640 int 641 midiioctl(dev, cmd, addr, flag, p) 642 dev_t dev; 643 u_long cmd; 644 caddr_t addr; 645 int flag; 646 struct proc *p; 647 { 648 int unit = MIDIUNIT(dev); 649 struct midi_softc *sc = midi_cd.cd_devs[unit]; 650 struct midi_hw_if *hw = sc->hw_if; 651 int error; 652 653 DPRINTF(("midiioctl: %p cmd=0x%08lx\n", sc, cmd)); 654 error = 0; 655 switch (cmd) { 656 case FIONBIO: 657 /* All handled in the upper FS layer. */ 658 break; 659 660 case FIOASYNC: 661 if (*(int *)addr) { 662 if (sc->async) 663 return (EBUSY); 664 sc->async = p; 665 DPRINTF(("midi_ioctl: FIOASYNC %p\n", p)); 666 } else 667 sc->async = 0; 668 break; 669 670 #if 0 671 case MIDI_PRETIME: 672 /* XXX OSS 673 * This should set up a read timeout, but that's 674 * why we have poll(), so there's nothing yet. */ 675 error = EINVAL; 676 break; 677 #endif 678 679 #ifdef MIDI_SAVE 680 case MIDI_GETSAVE: 681 error = copyout(&midisave, *(void **)addr, sizeof midisave); 682 break; 683 #endif 684 685 default: 686 if (hw->ioctl) 687 error = hw->ioctl(sc->hw_hdl, cmd, addr, flag, p); 688 else 689 error = EINVAL; 690 break; 691 } 692 return (error); 693 } 694 695 int 696 midiselect(dev, rw, p) 697 dev_t dev; 698 int rw; 699 struct proc *p; 700 { 701 int unit = MIDIUNIT(dev); 702 struct midi_softc *sc = midi_cd.cd_devs[unit]; 703 int s = splaudio(); 704 705 DPRINTF(("midiselect: %p rw=0x%x\n", sc, rw)); 706 707 switch (rw) { 708 case FREAD: 709 if (sc->inbuf.used > 0) { 710 splx(s); 711 return (1); 712 } 713 selrecord(p, &sc->rsel); 714 break; 715 716 case FWRITE: 717 if (sc->outbuf.used < sc->outbuf.usedhigh) { 718 splx(s); 719 return (1); 720 } 721 selrecord(p, &sc->wsel); 722 break; 723 } 724 725 splx(s); 726 return (0); 727 } 728 729 void 730 midi_getinfo(dev, mi) 731 dev_t dev; 732 struct midi_info *mi; 733 { 734 int unit = MIDIUNIT(dev); 735 struct midi_softc *sc; 736 737 if (unit >= midi_cd.cd_ndevs || 738 (sc = midi_cd.cd_devs[unit]) == NULL) 739 return; 740 sc->hw_if->getinfo(sc->hw_hdl, mi); 741 } 742 743 #endif /* NMIDI > 0 */ 744 745 #if NMIDI > 0 || NMIDIBUS > 0 746 747 int audioprint __P((void *, const char *)); 748 749 void 750 midi_attach_mi(mhwp, hdlp, dev) 751 struct midi_hw_if *mhwp; 752 void *hdlp; 753 struct device *dev; 754 { 755 struct audio_attach_args arg; 756 757 #ifdef DIAGNOSTIC 758 if (mhwp == NULL) { 759 printf("midi_attach_mi: NULL\n"); 760 return; 761 } 762 #endif 763 arg.type = AUDIODEV_TYPE_MIDI; 764 arg.hwif = mhwp; 765 arg.hdl = hdlp; 766 (void)config_found(dev, &arg, audioprint); 767 } 768 769 #endif /* NMIDI > 0 || NMIDIBUS > 0 */ 770