📄 opl3_midi.c
字号:
/* * Copyright (c) by Uros Bizjak <uros@kss-loka.si> * * Midi synth routines for OPL2/OPL3/OPL4 FM * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */#undef DEBUG_ALLOC#undef DEBUG_MIDI#include "opl3_voice.h"#include <sound/asoundef.h>extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];extern int use_internal_drums;/* * The next table looks magical, but it certainly is not. Its values have * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception * for i=0. This log-table converts a linear volume-scaling (0..127) to a * logarithmic scaling as present in the FM-synthesizer chips. so : Volume * 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative * volume -8 it was implemented as a table because it is only 128 bytes and * it saves a lot of log() calculations. (Rob Hooft <hooft@chem.ruu.nl>) */static char opl3_volume_table[128] ={ -63, -48, -40, -35, -32, -29, -27, -26, -24, -23, -21, -20, -19, -18, -18, -17, -16, -15, -15, -14, -13, -13, -12, -12, -11, -11, -10, -10, -10, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6, -6, -5, -5, -5, -5, -4, -4, -4, -4, -3, -3, -3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8};void snd_opl3_calc_volume(unsigned char *volbyte, int vel, snd_midi_channel_t *chan){ int oldvol, newvol, n; int volume; volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127); if (volume > 127) volume = 127; oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK); newvol = opl3_volume_table[volume] + oldvol; if (newvol > OPL3_TOTAL_LEVEL_MASK) newvol = OPL3_TOTAL_LEVEL_MASK; else if (newvol < 0) newvol = 0; n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK); *volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK);}/* * Converts the note frequency to block and fnum values for the FM chip */static short opl3_note_table[16] ={ 305, 323, /* for pitch bending, -2 semitones */ 343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647, 686, 726 /* for pitch bending, +2 semitones */};static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum, int note, snd_midi_channel_t *chan){ int block = ((note / 12) & 0x07) - 1; int idx = (note % 12) + 2; int freq; if (chan->midi_pitchbend) { int pitchbend = chan->midi_pitchbend; int segment; if (pitchbend > 0x1FFF) pitchbend = 0x1FFF; segment = pitchbend / 0x1000; freq = opl3_note_table[idx+segment]; freq += ((opl3_note_table[idx+segment+1] - freq) * (pitchbend % 0x1000)) / 0x1000; } else { freq = opl3_note_table[idx]; } *fnum = (unsigned char) freq; *blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) | ((block << 2) & OPL3_BLOCKNUM_MASK);}#ifdef DEBUG_ALLOCstatic void debug_alloc(opl3_t *opl3, char *s, int voice) { int i; char *str = "x.24"; printk("time %.5i: %s [%.2i]: ", opl3->use_time, s, voice); for (i = 0; i < opl3->max_voices; i++) printk("%c", *(str + opl3->voices[i].state + 1)); printk("\n");}#endif/* * Get a FM voice (channel) to play a note on. */static int opl3_get_voice(opl3_t *opl3, int instr_4op, snd_midi_channel_t *chan) { int chan_4op_1; /* first voice for 4op instrument */ int chan_4op_2; /* second voice for 4op instrument */ snd_opl3_voice_t *vp, *vp2; unsigned int voice_time; int i;#ifdef DEBUG_ALLOC char *alloc_type[3] = { "FREE ", "CHEAP ", "EXPENSIVE" };#endif /* This is our "allocation cost" table */ enum { FREE = 0, CHEAP, EXPENSIVE, END }; /* Keeps track of what we are finding */ struct best { unsigned int time; int voice; } best[END]; struct best *bp; for (i = 0; i < END; i++) { best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */; best[i].voice = -1; } /* Look through all the channels for the most suitable. */ for (i = 0; i < opl3->max_voices; i++) { vp = &opl3->voices[i]; if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL) /* skip unavailable channels, allocated by drum voices or by bounded 4op voices) */ continue; voice_time = vp->time; bp = best; chan_4op_1 = ((i < 3) || (i > 8 && i < 12)); chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15)); if (instr_4op) { /* allocate 4op voice */ /* skip channels unavailable to 4op instrument */ if (!chan_4op_1) continue; if (vp->state) /* kill one voice, CHEAP */ bp++; /* get state of bounded 2op channel to be allocated for 4op instrument */ vp2 = &opl3->voices[i + 3]; if (vp2->state == SNDRV_OPL3_ST_ON_2OP) { /* kill two voices, EXPENSIVE */ bp++; voice_time = (voice_time > vp->time) ? voice_time : vp->time; } } else { /* allocate 2op voice */ if ((chan_4op_1) || (chan_4op_2)) /* use bounded channels for 2op, CHEAP */ bp++; else if (vp->state) /* kill one voice on 2op channel, CHEAP */ bp++; /* raise kill cost to EXPENSIVE for all channels */ if (vp->state) bp++; } if (voice_time < bp->time) { bp->time = voice_time; bp->voice = i; } } for (i = 0; i < END; i++) { if (best[i].voice >= 0) {#ifdef DEBUG_ALLOC printk("%s %iop allocation on voice %i\n", alloc_type[i], instr_4op ? 4 : 2, best[i].voice);#endif return best[i].voice; } } /* not found */ return -1;}/* ------------------------------ *//* * System timer interrupt function */void snd_opl3_timer_func(unsigned long data){ opl3_t *opl3 = (opl3_t *)data; int again = 0; int i; spin_lock(&opl3->sys_timer_lock); for (i = 0; i < opl3->max_voices; i++) { snd_opl3_voice_t *vp = &opl3->voices[i]; if (vp->state > 0 && vp->note_off_check) { if (vp->note_off == jiffies) snd_opl3_note_off(opl3, vp->note, 0, vp->chan); else again++; } } if (again) { opl3->tlist.expires = jiffies + 1; /* invoke again */ add_timer(&opl3->tlist); } else { opl3->sys_timer_status = 0; } spin_unlock(&opl3->sys_timer_lock);}/* * Start system timer */static void snd_opl3_start_timer(opl3_t *opl3){ unsigned long flags; spin_lock_irqsave(&opl3->sys_timer_lock, flags); if (! opl3->sys_timer_status) { opl3->tlist.expires = jiffies + 1; add_timer(&opl3->tlist); opl3->sys_timer_status = 1; } spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);}/* ------------------------------ */static int snd_opl3_oss_map[MAX_OPL3_VOICES] = { 0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14};/* * Start a note. */void snd_opl3_note_on(void *p, int note, int vel, snd_midi_channel_t *chan){ opl3_t *opl3; snd_seq_instr_t wanted; snd_seq_kinstr_t *kinstr; int instr_4op; int voice; snd_opl3_voice_t *vp, *vp2; unsigned short connect_mask; unsigned char connection; unsigned char vol_op[4]; int extra_prg = 0; unsigned short reg_side; unsigned char op_offset; unsigned char voice_offset; unsigned short opl3_reg; unsigned char reg_val; int key = note; unsigned char fnum, blocknum; int i; fm_instrument_t *fm; unsigned long flags; opl3 = p;#ifdef DEBUG_MIDI snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n", chan->number, chan->midi_program, note, vel);#endif wanted.cluster = 0; wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3; /* in SYNTH mode, application takes care of voices */ /* in SEQ mode, drum voice numbers are notes on drum channel */ if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { if (chan->drum_channel) { /* percussion instruments are located in bank 128 */ wanted.bank = 128; wanted.prg = note; } else { wanted.bank = chan->gm_bank_select; wanted.prg = chan->midi_program; } } else { /* Prepare for OSS mode */ if (chan->number >= MAX_OPL3_VOICES) return; /* OSS instruments are located in bank 127 */ wanted.bank = 127; wanted.prg = chan->midi_program; } spin_lock_irqsave(&opl3->voice_lock, flags); if (use_internal_drums) { snd_opl3_drum_switch(opl3, note, vel, 1, chan); spin_unlock_irqrestore(&opl3->voice_lock, flags); return; } __extra_prg: kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0); if (kinstr == NULL) { spin_unlock_irqrestore(&opl3->voice_lock, flags); return; } fm = KINSTR_DATA(kinstr); switch (fm->type) { case FM_PATCH_OPL2: instr_4op = 0; break; case FM_PATCH_OPL3: if (opl3->hardware >= OPL3_HW_OPL3) { instr_4op = 1; break; } default: snd_seq_instr_free_use(opl3->ilist, kinstr); spin_unlock_irqrestore(&opl3->voice_lock, flags); return; }#ifdef DEBUG_MIDI snd_printk(" --> OPL%i instrument: %s\n", instr_4op ? 3 : 2, kinstr->name);#endif /* in SYNTH mode, application takes care of voices */ /* in SEQ mode, allocate voice on free OPL3 channel */ if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { voice = opl3_get_voice(opl3, instr_4op, chan); } else { /* remap OSS voice */ voice = snd_opl3_oss_map[chan->number]; } if (voice < MAX_OPL2_VOICES) { /* Left register block for voices 0 .. 8 */ reg_side = OPL3_LEFT; voice_offset = voice; connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07; } else { /* Right register block for voices 9 .. 17 */ reg_side = OPL3_RIGHT; voice_offset = voice - MAX_OPL2_VOICES; connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38; } /* kill voice on channel */ vp = &opl3->voices[voice]; if (vp->state > 0) { opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT; opl3->command(opl3, opl3_reg, reg_val); } if (instr_4op) { vp2 = &opl3->voices[voice + 3]; if (vp->state > 0) { opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset + 3); reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT; opl3->command(opl3, opl3_reg, reg_val); } } /* set connection register */ if (instr_4op) { if ((opl3->connection_reg ^ connect_mask) & connect_mask) { opl3->connection_reg |= connect_mask; /* set connection bit */ opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT; opl3->command(opl3, opl3_reg, opl3->connection_reg); } } else { if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) { opl3->connection_reg &= ~connect_mask; /* clear connection bit */ opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT; opl3->command(opl3, opl3_reg, opl3->connection_reg); } }#ifdef DEBUG_MIDI
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -