xref: /netbsd/sys/dev/ic/opl.c (revision bf9ec67e)
1 /*	$NetBSD: opl.c,v 1.17 2001/11/13 13:14:42 lukem Exp $	*/
2 
3 /*
4  * Copyright (c) 1998 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Lennart Augustsson (augustss@netbsd.org).
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 
39 /*
40  * The OPL3 (YMF262) manual can be found at
41  * ftp://ftp.yamahayst.com/Fax_Back_Doc/sound/YMF262.PDF
42  */
43 
44 #include <sys/cdefs.h>
45 __KERNEL_RCSID(0, "$NetBSD: opl.c,v 1.17 2001/11/13 13:14:42 lukem Exp $");
46 
47 #include <sys/param.h>
48 #include <sys/systm.h>
49 #include <sys/errno.h>
50 #include <sys/ioctl.h>
51 #include <sys/syslog.h>
52 #include <sys/device.h>
53 #include <sys/select.h>
54 
55 #include <machine/cpu.h>
56 #include <machine/bus.h>
57 
58 #include <sys/audioio.h>
59 #include <sys/midiio.h>
60 #include <dev/audio_if.h>
61 
62 #include <dev/midi_if.h>
63 #include <dev/midivar.h>
64 #include <dev/midisynvar.h>
65 
66 #include <dev/ic/oplreg.h>
67 #include <dev/ic/oplvar.h>
68 
69 #ifdef AUDIO_DEBUG
70 #define DPRINTF(x)	if (opldebug) printf x
71 #define DPRINTFN(n,x)	if (opldebug >= (n)) printf x
72 int	opldebug = 0;
73 #else
74 #define DPRINTF(x)
75 #define DPRINTFN(n,x)
76 #endif
77 
78 struct real_voice {
79 	u_int8_t voice_num;
80 	u_int8_t voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */
81 	u_int8_t iooffs; /* I/O port (left or right side) */
82 	u_int8_t op[4]; /* Operator offsets */
83 };
84 
85 const struct opl_voice voicetab[] = {
86 /*       No    I/O offs	OP1	OP2	OP3   OP4	*/
87 /*	---------------------------------------------	*/
88 	{ 0,   OPL_L,	{0x00,	0x03,	0x08, 0x0b}},
89 	{ 1,   OPL_L,	{0x01,	0x04,	0x09, 0x0c}},
90 	{ 2,   OPL_L,	{0x02,	0x05,	0x0a, 0x0d}},
91 
92 	{ 3,   OPL_L,	{0x08,	0x0b,	0x00, 0x00}},
93 	{ 4,   OPL_L,	{0x09,	0x0c,	0x00, 0x00}},
94 	{ 5,   OPL_L,	{0x0a,	0x0d,	0x00, 0x00}},
95 
96 	{ 6,   OPL_L,	{0x10,	0x13,	0x00, 0x00}},
97 	{ 7,   OPL_L,	{0x11,	0x14,	0x00, 0x00}},
98 	{ 8,   OPL_L,	{0x12,	0x15,	0x00, 0x00}},
99 
100 	{ 0,   OPL_R,	{0x00,	0x03,	0x08, 0x0b}},
101 	{ 1,   OPL_R,	{0x01,	0x04,	0x09, 0x0c}},
102 	{ 2,   OPL_R,	{0x02,	0x05,	0x0a, 0x0d}},
103 	{ 3,   OPL_R,	{0x08,	0x0b,	0x00, 0x00}},
104 	{ 4,   OPL_R,	{0x09,	0x0c,	0x00, 0x00}},
105 	{ 5,   OPL_R,	{0x0a,	0x0d,	0x00, 0x00}},
106 
107 	{ 6,   OPL_R,	{0x10,	0x13,	0x00, 0x00}},
108 	{ 7,   OPL_R,	{0x11,	0x14,	0x00, 0x00}},
109 	{ 8,   OPL_R,	{0x12,	0x15,	0x00, 0x00}}
110 };
111 
112 static void opl_command(struct opl_softc *, int, int, int);
113 void opl_reset(struct opl_softc *);
114 void opl_freq_to_fnum (int freq, int *block, int *fnum);
115 
116 int oplsyn_open __P((midisyn *ms, int));
117 void oplsyn_close __P((midisyn *));
118 void oplsyn_reset __P((void *));
119 void oplsyn_noteon __P((midisyn *, u_int32_t, u_int32_t, u_int32_t));
120 void oplsyn_noteoff __P((midisyn *, u_int32_t, u_int32_t, u_int32_t));
121 void oplsyn_keypressure __P((midisyn *, u_int32_t, u_int32_t, u_int32_t));
122 void oplsyn_ctlchange __P((midisyn *, u_int32_t, u_int32_t, u_int32_t));
123 void oplsyn_programchange __P((midisyn *, u_int32_t, u_int32_t));
124 void oplsyn_pitchbend __P((midisyn *, u_int32_t, u_int32_t, u_int32_t));
125 void oplsyn_loadpatch __P((midisyn *, struct sysex_info *, struct uio *));
126 
127 
128 void opl_set_op_reg __P((struct opl_softc *, int, int, int, u_char));
129 void opl_set_ch_reg __P((struct opl_softc *, int, int, u_char));
130 void opl_load_patch __P((struct opl_softc *, int));
131 u_int32_t opl_get_block_fnum __P((int freq));
132 int opl_calc_vol __P((int regbyte, int volume, int main_vol));
133 
134 struct midisyn_methods opl3_midi = {
135 	oplsyn_open,
136 	oplsyn_close,
137 	0,
138 	0,
139 	oplsyn_noteon,
140 	oplsyn_noteoff,
141 	oplsyn_keypressure,
142 	oplsyn_ctlchange,
143 	oplsyn_programchange,
144 	0,
145 	oplsyn_pitchbend,
146 	0
147 };
148 
149 void
150 opl_attach(sc)
151 	struct opl_softc *sc;
152 {
153 	int i;
154 
155 	if (!opl_find(sc)) {
156 		printf("\nopl: find failed\n");
157 		return;
158 	}
159 
160 	sc->syn.mets = &opl3_midi;
161 	sprintf(sc->syn.name, "%sYamaha OPL%d", sc->syn.name, sc->model);
162 	sc->syn.data = sc;
163 	sc->syn.nvoice = sc->model == OPL_2 ? OPL2_NVOICE : OPL3_NVOICE;
164 	sc->syn.flags =  MS_DOALLOC | MS_FREQXLATE;
165 	midisyn_attach(&sc->mididev, &sc->syn);
166 
167 	/* Set up voice table */
168 	for (i = 0; i < OPL3_NVOICE; i++)
169 		sc->voices[i] = voicetab[i];
170 
171 	opl_reset(sc);
172 
173 	printf(": model OPL%d", sc->model);
174 
175 	/* Set up panpot */
176 	for (i = 0; i < MIDI_MAX_CHANS; i++)
177 		sc->pan[i] = OPL_VOICE_TO_LEFT | OPL_VOICE_TO_RIGHT;
178 	sc->panl = OPL_VOICE_TO_LEFT;
179 	sc->panr = OPL_VOICE_TO_RIGHT;
180 	if (sc->model == OPL_3 &&
181 	    sc->mididev.dev.dv_cfdata->cf_flags & OPL_FLAGS_SWAP_LR) {
182 		sc->panl = OPL_VOICE_TO_RIGHT;
183 		sc->panr = OPL_VOICE_TO_LEFT;
184 		printf(": LR swapped");
185 	}
186 
187 	printf("\n");
188 
189 	sc->sc_mididev =
190 	    midi_attach_mi(&midisyn_hw_if, &sc->syn, &sc->mididev.dev);
191 }
192 
193 int
194 opl_detach(sc, flags)
195 	struct opl_softc *sc;
196 	int flags;
197 {
198 	int rv = 0;
199 
200 	if (sc->sc_mididev != NULL)
201 		rv = config_detach(sc->sc_mididev, flags);
202 
203 	return(rv);
204 }
205 
206 static void
207 opl_command(sc, offs, addr, data)
208 	struct opl_softc *sc;
209 	int offs;
210 	int addr, data;
211 {
212 	DPRINTFN(4, ("opl_command: sc=%p, offs=%d addr=0x%02x data=0x%02x\n",
213 		     sc, offs, addr, data));
214 	offs += sc->offs;
215 	bus_space_write_1(sc->iot, sc->ioh, OPL_ADDR+offs, addr);
216 	if (sc->model == OPL_2)
217 		delay(10);
218 	else
219 		delay(6);
220 	bus_space_write_1(sc->iot, sc->ioh, OPL_DATA+offs, data);
221 	if (sc->model == OPL_2)
222 		delay(30);
223 	else
224 		delay(6);
225 }
226 
227 int
228 opl_find(sc)
229 	struct opl_softc *sc;
230 {
231 	u_int8_t status1, status2;
232 
233 	DPRINTFN(2,("opl_find: ioh=0x%x\n", (int)sc->ioh));
234 	sc->model = OPL_2;	/* worst case assumtion */
235 
236 	/* Reset timers 1 and 2 */
237 	opl_command(sc, OPL_L, OPL_TIMER_CONTROL,
238 		    OPL_TIMER1_MASK | OPL_TIMER2_MASK);
239 	/* Reset the IRQ of the FM chip */
240 	opl_command(sc, OPL_L, OPL_TIMER_CONTROL, OPL_IRQ_RESET);
241 
242 	/* get status bits */
243 	status1 = bus_space_read_1(sc->iot,sc->ioh,OPL_STATUS+OPL_L+sc->offs);
244 
245 	opl_command(sc, OPL_L, OPL_TIMER1, -2); /* wait 2 ticks */
246 	opl_command(sc, OPL_L, OPL_TIMER_CONTROL, /* start timer1 */
247 		    OPL_TIMER1_START | OPL_TIMER2_MASK);
248 	delay(1000);		/* wait for timer to expire */
249 
250 	/* get status bits again */
251 	status2 = bus_space_read_1(sc->iot,sc->ioh,OPL_STATUS+OPL_L+sc->offs);
252 
253 	opl_command(sc, OPL_L, OPL_TIMER_CONTROL,
254 		    OPL_TIMER1_MASK | OPL_TIMER2_MASK);
255 	opl_command(sc, OPL_L, OPL_TIMER_CONTROL, OPL_IRQ_RESET);
256 
257 	DPRINTFN(2,("opl_find: %02x %02x\n", status1, status2));
258 
259 	if ((status1 & OPL_STATUS_MASK) != 0 ||
260 	    (status2 & OPL_STATUS_MASK) != (OPL_STATUS_IRQ | OPL_STATUS_FT1))
261 		return (0);
262 
263 	switch(status1) {
264 	case 0x00:
265 	case 0x0f:
266 		sc->model = OPL_3;
267 		break;
268 	case 0x06:
269 		sc->model = OPL_2;
270 		break;
271 	default:
272 		return (0);
273 	}
274 
275 	DPRINTFN(2,("opl_find: OPL%d at 0x%x detected\n",
276 		    sc->model, (int)sc->ioh));
277 	return (1);
278 }
279 
280 void
281 opl_set_op_reg(sc, base, voice, op, value)
282 	struct opl_softc *sc;
283 	int base;
284 	int voice;
285 	int op;
286 	u_char value;
287 {
288 	struct opl_voice *v = &sc->voices[voice];
289 	opl_command(sc, v->iooffs, base + v->op[op], value);
290 }
291 
292 void
293 opl_set_ch_reg(sc, base, voice, value)
294 	struct opl_softc *sc;
295 	int base;
296 	int voice;
297 	u_char value;
298 {
299 	struct opl_voice *v = &sc->voices[voice];
300 	opl_command(sc, v->iooffs, base + v->voiceno, value);
301 }
302 
303 
304 void
305 opl_load_patch(sc, v)
306 	struct opl_softc *sc;
307 	int v;
308 {
309 	const struct opl_operators *p = sc->voices[v].patch;
310 
311 	opl_set_op_reg(sc, OPL_AM_VIB,          v, 0, p->ops[OO_CHARS+0]);
312 	opl_set_op_reg(sc, OPL_AM_VIB,          v, 1, p->ops[OO_CHARS+1]);
313 	opl_set_op_reg(sc, OPL_KSL_LEVEL,       v, 0, p->ops[OO_KSL_LEV+0]);
314 	opl_set_op_reg(sc, OPL_KSL_LEVEL,       v, 1, p->ops[OO_KSL_LEV+1]);
315 	opl_set_op_reg(sc, OPL_ATTACK_DECAY,    v, 0, p->ops[OO_ATT_DEC+0]);
316 	opl_set_op_reg(sc, OPL_ATTACK_DECAY,    v, 1, p->ops[OO_ATT_DEC+1]);
317 	opl_set_op_reg(sc, OPL_SUSTAIN_RELEASE, v, 0, p->ops[OO_SUS_REL+0]);
318 	opl_set_op_reg(sc, OPL_SUSTAIN_RELEASE, v, 1, p->ops[OO_SUS_REL+1]);
319 	opl_set_op_reg(sc, OPL_WAVE_SELECT,     v, 0, p->ops[OO_WAV_SEL+0]);
320 	opl_set_op_reg(sc, OPL_WAVE_SELECT,     v, 1, p->ops[OO_WAV_SEL+1]);
321 	opl_set_ch_reg(sc, OPL_FEEDBACK_CONNECTION, v, p->ops[OO_FB_CONN]);
322 }
323 
324 #define OPL_FNUM_FAIL 0xffff
325 u_int32_t
326 opl_get_block_fnum(freq)
327 	int freq;
328 {
329 	u_int32_t f_num = freq / 3125;
330 	u_int32_t  block = 0;
331 
332 	while (f_num > 0x3ff && block < 8) {
333 		block++;
334 		f_num >>= 1;
335 	}
336 
337 	if (block > 7)
338 		return (OPL_FNUM_FAIL);
339 	else
340 		return ((block << 10) | f_num);
341   }
342 
343 
344 void
345 opl_reset(sc)
346 	struct opl_softc *sc;
347 {
348 	int i;
349 
350 	for (i = 1; i <= OPL_MAXREG; i++)
351 		opl_command(sc, OPL_L, OPL_KEYON_BLOCK + i, 0);
352 
353 	opl_command(sc, OPL_L, OPL_TEST, OPL_ENABLE_WAVE_SELECT);
354 	opl_command(sc, OPL_L, OPL_PERCUSSION, 0);
355 	if (sc->model == OPL_3) {
356 		opl_command(sc, OPL_R, OPL_MODE, OPL3_ENABLE);
357 		opl_command(sc, OPL_R,OPL_CONNECTION_SELECT,OPL_NOCONNECTION);
358 	}
359 
360 	sc->volume = 64;
361 }
362 
363 int
364 oplsyn_open(ms, flags)
365 	midisyn *ms;
366 	int flags;
367 {
368 	struct opl_softc *sc = ms->data;
369 
370 	DPRINTFN(2, ("oplsyn_open: %d\n", flags));
371 
372 #ifndef AUDIO_NO_POWER_CTL
373 	if (sc->powerctl)
374 		sc->powerctl(sc->powerarg, 1);
375 #endif
376 	opl_reset(ms->data);
377 	if (sc->spkrctl)
378 		sc->spkrctl(sc->spkrarg, 1);
379 	return (0);
380 }
381 
382 void
383 oplsyn_close(ms)
384 	midisyn *ms;
385 {
386 	struct opl_softc *sc = ms->data;
387 
388 	DPRINTFN(2, ("oplsyn_close:\n"));
389 
390 	/*opl_reset(ms->data);*/
391 	if (sc->spkrctl)
392 		sc->spkrctl(sc->spkrarg, 0);
393 #ifndef AUDIO_NO_POWER_CTL
394 	if (sc->powerctl)
395 		sc->powerctl(sc->powerarg, 0);
396 #endif
397 }
398 
399 #if 0
400 void
401 oplsyn_getinfo(addr, sd)
402 	void *addr;
403 	struct synth_dev *sd;
404 {
405 	struct opl_softc *sc = addr;
406 
407 	sd->name = sc->model == OPL_2 ? "Yamaha OPL2" : "Yamaha OPL3";
408 	sd->type = SYNTH_TYPE_FM;
409 	sd->subtype = sc->model == OPL_2 ? SYNTH_SUB_FM_TYPE_ADLIB
410 		: SYNTH_SUB_FM_TYPE_OPL3;
411 	sd->capabilities = 0;
412 }
413 #endif
414 
415 void
416 oplsyn_reset(addr)
417 	void *addr;
418 {
419 	struct opl_softc *sc = addr;
420 	DPRINTFN(3, ("oplsyn_reset:\n"));
421 	opl_reset(sc);
422 }
423 
424 const int8_t opl_volume_table[128] =
425     {-64, -48, -40, -35, -32, -29, -27, -26,
426      -24, -23, -21, -20, -19, -18, -18, -17,
427      -16, -15, -15, -14, -13, -13, -12, -12,
428      -11, -11, -10, -10, -10, -9, -9, -8,
429      -8, -8, -7, -7, -7, -6, -6, -6,
430      -5, -5, -5, -5, -4, -4, -4, -4,
431      -3, -3, -3, -3, -2, -2, -2, -2,
432      -2, -1, -1, -1, -1, 0, 0, 0,
433      0, 0, 0, 1, 1, 1, 1, 1,
434      1, 2, 2, 2, 2, 2, 2, 2,
435      3, 3, 3, 3, 3, 3, 3, 4,
436      4, 4, 4, 4, 4, 4, 4, 5,
437      5, 5, 5, 5, 5, 5, 5, 5,
438      6, 6, 6, 6, 6, 6, 6, 6,
439      6, 7, 7, 7, 7, 7, 7, 7,
440      7, 7, 7, 8, 8, 8, 8, 8};
441 
442 int
443 opl_calc_vol(regbyte, volume, mainvol)
444 	int regbyte;
445 	int volume;
446 	int mainvol;
447 {
448 	int level = ~regbyte & OPL_TOTAL_LEVEL_MASK;
449 
450 	if (mainvol > 127)
451 		mainvol = 127;
452 
453 	volume = (volume * mainvol) / 127;
454 
455 	if (level)
456 		level += opl_volume_table[volume];
457 
458 	if (level > OPL_TOTAL_LEVEL_MASK)
459 		level = OPL_TOTAL_LEVEL_MASK;
460 	if (level < 0)
461 		level = 0;
462 
463 	return (~level & OPL_TOTAL_LEVEL_MASK);
464 }
465 
466 void
467 oplsyn_noteon(ms, voice, freq, vel)
468 	midisyn *ms;
469 	u_int32_t voice, freq, vel;
470 {
471 	struct opl_softc *sc = ms->data;
472 	struct opl_voice *v;
473 	const struct opl_operators *p;
474 	u_int32_t block_fnum;
475 	int mult;
476 	int c_mult, m_mult;
477 	u_int32_t chan;
478 	u_int8_t chars0, chars1, ksl0, ksl1, fbc;
479 	u_int8_t r20m, r20c, r40m, r40c, rA0, rB0;
480 	u_int8_t vol0, vol1;
481 
482 	DPRINTFN(3, ("oplsyn_noteon: %p %d %d\n", sc, voice,
483 		     MIDISYN_FREQ_TO_HZ(freq)));
484 
485 #ifdef DIAGNOSTIC
486 	if (voice < 0 || voice >= sc->syn.nvoice) {
487 		printf("oplsyn_noteon: bad voice %d\n", voice);
488 		return;
489 	}
490 #endif
491 	/* Turn off old note */
492 	opl_set_op_reg(sc, OPL_KSL_LEVEL,   voice, 0, 0xff);
493 	opl_set_op_reg(sc, OPL_KSL_LEVEL,   voice, 1, 0xff);
494 	opl_set_ch_reg(sc, OPL_KEYON_BLOCK, voice,    0);
495 
496 	v = &sc->voices[voice];
497 
498 	chan = MS_GETCHAN(&ms->voices[voice]);
499 	p = &opl2_instrs[ms->pgms[chan]];
500 	v->patch = p;
501 	opl_load_patch(sc, voice);
502 
503 	mult = 1;
504 	for (;;) {
505 		block_fnum = opl_get_block_fnum(freq / mult);
506 		if (block_fnum != OPL_FNUM_FAIL)
507 			break;
508 		mult *= 2;
509 		if (mult == 16)
510 			mult = 15;
511 	}
512 
513 	chars0 = p->ops[OO_CHARS+0];
514 	chars1 = p->ops[OO_CHARS+1];
515 	m_mult = (chars0 & OPL_MULTIPLE_MASK) * mult;
516 	c_mult = (chars1 & OPL_MULTIPLE_MASK) * mult;
517 	if ((block_fnum == OPL_FNUM_FAIL) || (m_mult > 15) || (c_mult > 15)) {
518 		printf("oplsyn_noteon: frequency out of range %d\n",
519 		       MIDISYN_FREQ_TO_HZ(freq));
520 		return;
521 	}
522 	r20m = (chars0 &~ OPL_MULTIPLE_MASK) | m_mult;
523 	r20c = (chars1 &~ OPL_MULTIPLE_MASK) | c_mult;
524 
525 	/* 2 voice */
526 	ksl0 = p->ops[OO_KSL_LEV+0];
527 	ksl1 = p->ops[OO_KSL_LEV+1];
528 	if (p->ops[OO_FB_CONN] & 0x01) {
529 		vol0 = opl_calc_vol(ksl0, vel, sc->volume);
530 		vol1 = opl_calc_vol(ksl1, vel, sc->volume);
531 	} else {
532 		vol0 = ksl0;
533 		vol1 = opl_calc_vol(ksl1, vel, sc->volume);
534 	}
535 	r40m = (ksl0 & OPL_KSL_MASK) | vol0;
536 	r40c = (ksl1 & OPL_KSL_MASK) | vol1;
537 
538 	rA0  = block_fnum & 0xFF;
539 	rB0  = (block_fnum >> 8) | OPL_KEYON_BIT;
540 
541 	v->rB0 = rB0;
542 
543 	fbc = p->ops[OO_FB_CONN];
544 	if (sc->model == OPL_3) {
545 		fbc &= ~OPL_STEREO_BITS;
546 		fbc |= sc->pan[chan];
547 	}
548 	opl_set_ch_reg(sc, OPL_FEEDBACK_CONNECTION, voice, fbc);
549 
550 	opl_set_op_reg(sc, OPL_AM_VIB,      voice, 0, r20m);
551 	opl_set_op_reg(sc, OPL_AM_VIB,      voice, 1, r20c);
552 	opl_set_op_reg(sc, OPL_KSL_LEVEL,   voice, 0, r40m);
553 	opl_set_op_reg(sc, OPL_KSL_LEVEL,   voice, 1, r40c);
554 	opl_set_ch_reg(sc, OPL_FNUM_LOW,    voice,    rA0);
555 	opl_set_ch_reg(sc, OPL_KEYON_BLOCK, voice,    rB0);
556 }
557 
558 void
559 oplsyn_noteoff(ms, voice, note, vel)
560 	midisyn *ms;
561 	u_int32_t voice, note, vel;
562 {
563 	struct opl_softc *sc = ms->data;
564 	struct opl_voice *v;
565 
566 	DPRINTFN(3, ("oplsyn_noteoff: %p %d %d\n", sc, voice,
567 		     MIDISYN_FREQ_TO_HZ(note)));
568 
569 #ifdef DIAGNOSTIC
570 	if (voice < 0 || voice >= sc->syn.nvoice) {
571 		printf("oplsyn_noteoff: bad voice %d\n", voice);
572 		return;
573 	}
574 #endif
575 	v = &sc->voices[voice];
576 	opl_set_ch_reg(sc, 0xB0, voice, v->rB0 & ~OPL_KEYON_BIT);
577 }
578 
579 void
580 oplsyn_keypressure(ms, voice, note, vel)
581 	midisyn *ms;
582 	u_int32_t voice, note, vel;
583 {
584 #ifdef AUDIO_DEBUG
585 	struct opl_softc *sc = ms->data;
586 	DPRINTFN(1, ("oplsyn_keypressure: %p %d\n", sc, note));
587 #endif
588 }
589 
590 void
591 oplsyn_ctlchange(ms, chan, parm, w14)
592 	midisyn *ms;
593 	u_int32_t chan, parm, w14;
594 {
595 	struct opl_softc *sc = ms->data;
596 
597 	DPRINTFN(1, ("oplsyn_ctlchange: %p %d\n", sc, chan));
598 	switch (parm) {
599 	case MIDI_CTRL_PAN_MSB:
600 		sc->pan[chan] =
601 		    (w14 <= OPL_MIDI_CENTER_MAX ? sc->panl : 0) |
602 		    (w14 >= OPL_MIDI_CENTER_MIN ? sc->panr : 0);
603 		break;
604 	}
605 }
606 
607 /* PROGRAM CHANGE midi event: */
608 void
609 oplsyn_programchange(ms, chan, prog)
610 	midisyn *ms;
611 	u_int32_t chan;
612 	u_int32_t prog;
613 {
614 	/* sanity checks */
615 	if (chan >= MIDI_MAX_CHANS || prog >= OPL_NINSTR)
616 		return;
617 
618 	ms->pgms[chan] = prog;
619 }
620 
621 void
622 oplsyn_pitchbend(ms, voice, parm, x)
623 	midisyn *ms;
624 	u_int32_t voice, parm, x;
625 {
626 #ifdef AUDIO_DEBUG
627 	struct opl_softc *sc = ms->data;
628 	DPRINTFN(1, ("oplsyn_pitchbend: %p %d\n", sc, voice));
629 #endif
630 }
631 
632 void
633 oplsyn_loadpatch(ms, sysex, uio)
634 	midisyn *ms;
635 	struct sysex_info *sysex;
636 	struct uio *uio;
637 {
638 #if 0
639 	struct opl_softc *sc = ms->data;
640 	struct sbi_instrument ins;
641 
642 	DPRINTFN(1, ("oplsyn_loadpatch: %p\n", sc));
643 
644 	memcpy(&ins, sysex, sizeof *sysex);
645 	if (uio->uio_resid >= sizeof ins - sizeof *sysex)
646 		return EINVAL;
647 	uiomove((char *)&ins + sizeof *sysex, sizeof ins - sizeof *sysex, uio);
648 	/* XXX */
649 #endif
650 }
651