📄 lkeymidi.cpp
字号:
/**************************************************************************
* LinDevelop v1.2
* The core MIDI file player.
* written by Lin Wei, 2000
**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#include <limits.h>
#include <lkey.h>
#include <lsys.h>
#define TRUE 1
#define FALSE 0
#define MIDI_LAYERS 4
#define MIDI_TRACKS 32
////////////////////////////////////////////////////////////////////////////
char *lmMidiMapFile[2]={ "","" };
#define TIMERS_PER_SECOND 1193181L
#define SECS_TO_TIMER(x) ((long)(x) * TIMERS_PER_SECOND)
#define MSEC_TO_TIMER(x) ((long)(x) * (TIMERS_PER_SECOND / 1000))
#define BPS_TO_TIMER(x) (TIMERS_PER_SECOND / (long)(x))
#define BPM_TO_TIMER(x) ((60 * TIMERS_PER_SECOND) / (long)(x))
typedef struct MIDI_DRIVER /* driver for playing midi music */
{
char *name; /* driver name */
char *desc; /* description string */
int voices; /* available voices */
int basevoice; /* voice number offset */
int max_voices; /* maximum voices we can support */
int def_voices; /* default number of voices to use */
int xmin, xmax; /* reserved voice range */
/* setup routines */
int (*detect)();
int (*init)(int voices);
void (*exit)();
int (*mixer_volume)(int volume);
/* raw MIDI output to MPU-401, etc. */
void (*raw_midi)(unsigned char data);
/* dynamic patch loading routines */
int (*load_patches)(char *patches, char *drums);
void (*adjust_patches)(char *patches, char *drums);
/* note control functions */
void (*key_on)(int inst, int note, int bend, int vol, int pan);
void (*key_off)(int voice);
void (*set_volume)(int voice, int vol);
void (*set_pitch)(int voice, int note, int bend);
void (*set_pan)(int voice, int pan);
void (*set_vibrato)(int voice, int amount);
} MIDI_DRIVER;
extern volatile long _midi_tick;
extern MIDI_DRIVER *midi_driver;
#define MIDI_NONE 0
#define MIDI_OPL2 1
#define MIDI_OPL3 2
#define MIDI_2XOPL2 3
////////////////////////////////////////////////////////////////////////////
typedef struct MIDI_TRACK /* a track in the MIDI file */
{
unsigned char *pos; /* position in track data */
long timer; /* time until next event */
unsigned char running_status; /* last MIDI event */
} MIDI_TRACK;
typedef struct MIDI_CHANNEL /* a MIDI channel */
{
int patch; /* current sound */
int volume; /* volume controller */
int pan; /* pan position */
int pitch_bend; /* pitch bend position */
int new_volume; /* cached volume change */
int new_pitch_bend; /* cached pitch bend */
int note[128][MIDI_LAYERS]; /* status of each note */
} MIDI_CHANNEL;
typedef struct MIDI_VOICE /* a voice on the soundcard */
{
int channel; /* MIDI channel */
int note; /* note (-1 = off) */
int volume; /* note velocity */
int time; /* when note was triggered */
} MIDI_VOICE;
typedef struct WAITING_NOTE /* a stored note-on request */
{
int channel;
int note;
int volume;
} WAITING_NOTE;
typedef struct PATCH_TABLE /* GM -> external synth */
{
int bank1; /* controller #0 */
int bank2; /* controller #32 */
int prog; /* program change */
int pitch; /* pitch shift */
} PATCH_TABLE;
volatile long midi_pos = -1; /* position in MIDI file */
static long midi_pos_counter; /* delta for midi_pos */
volatile long _midi_tick = 0; /* counter for killing notes */
static void midi_player(); /* core MIDI player routine */
static void prepare_to_play(MIDI *midi);
static void midi_lock_mem();
static MIDI *midifile = NULL; /* the file that is playing */
static short midi_loop = 0; /* repeat at eof? */
long midi_loop_start = -1; /* where to loop back to */
long midi_loop_end = -1; /* loop at this position */
static int midi_semaphore = 0; /* reentrancy flag */
static int midi_loaded_patches = FALSE; /* loaded entire patch set? */
static long midi_timer_speed; /* midi_player's timer speed */
static int midi_pos_speed; /* MIDI delta -> midi_pos */
static int midi_speed; /* MIDI delta -> timer */
static int midi_new_speed; /* for tempo change events */
static int old_midi_volume = -1; /* stored global volume */
short lm_midi_volume=255;
static int midi_alloc_channel; /* so _midi_allocate_voice */
static int midi_alloc_note; /* knows which note the */
static int midi_alloc_vol; /* sound is associated with */
static MIDI_TRACK midi_track[MIDI_TRACKS]; /* the active tracks */
static MIDI_VOICE midi_voice[MIDI_VOICES]; /* synth voice status */
static MIDI_CHANNEL midi_channel[16]; /* MIDI channel info */
static WAITING_NOTE midi_waiting[MIDI_VOICES]; /* notes still to be played */
static PATCH_TABLE patch_table[128]; /* GM -> external synth */
static short midi_seeking; /* set during seeks */
static short midi_looping; /* set during loops */
void (*midi_msg_callback)(int msg, int byte1, int byte2) = NULL;
void (*midi_meta_callback)(int type, unsigned char *data, int length) = NULL;
void (*midi_sysex_callback)(unsigned char *data, int length) = NULL;
MIDI_DRIVER *midi_driver;
////////////////////////////////////////////////////////////////////////////
static char lmMidiFlag=0;
char lm_error_msg[150];
char lmMidiInit(char driver);
char lmMidiRest();
void lmFM_load_map(int choice,int drums);
int lmFM_load_ibk(char *filename, int drums);
MIDI *lmLoadMidi(char *fname,long file_offset);
void lmDestroyMidi(MIDI*);
int lmMidiPlay(MIDI *midi, int loop);
void lmStopMidi();
void lmMidiPause();
int lmMidiSeek(int target);
void lmMidiResume();
////////////////////////////////////////////////////////////////////////////
static int midi_init(char driver);
static void midi_exit();
char lmMidiInit(char driver)
{ if (lmMidiFlag) return 1;
if (!lk_sb_port) { sprintf(lsys_message,"Sound card init failed\n"); return 0; }
if (!midi_init(driver)) return FALSE;
lmMidiFlag=1; return 1;
}
char lmMidiRest()
{ if (!lmMidiFlag) return 0;
midi_exit();
lmMidiFlag=0;
return 1;
}
static int mfgetw(FILE *f)
{ unsigned short w[2],i;
for (i=0;i<2;i++) w[i]=fgetc(f);
return (unsigned)w[0]*256+w[1];
}
static long mfgetl(FILE *f)
{ unsigned w[4],i;
for (i=0;i<4;i++) w[i]=fgetc(f);
return (w[0]*256+w[1])*65536L+w[2]*256+w[3];
}
void lmDestroyMidi(MIDI *);
void lmStopMidi() { lmMidiPlay(NULL, FALSE); }
MIDI *lmLoadMidi(char *filename,long file_offset)
{
int c;
char buf[256];
long data,retsiz;
FILE *fp;
MIDI *midi;
int num_tracks;
fp = fopen(filename, "rb"); /* open the file */
if (!fp) return NULL;
midi = (MIDI*)malloc(sizeof(MIDI)); /* get some memory */
if (!midi) {
fclose(fp); return NULL;
}
for (c=0; c<MIDI_TRACKS; c++) {
midi->track[c].data = NULL;
midi->track[c].len = 0;
}
fseek(fp, file_offset, SEEK_SET);
fread(buf, 4, 1, fp); /* read midi header */
if (memcmp(buf, "MThd", 4)) goto err;
mfgetl(fp); /* skip header chunk length */
data = mfgetw(fp); /* MIDI file type */
if ((data != 0) && (data != 1)) goto err;
num_tracks = mfgetw(fp); /* number of tracks */
if ((num_tracks < 1) || (num_tracks > MIDI_TRACKS)) goto err;
data = mfgetw(fp); /* beat divisions */
midi->divisions = abs(data);
for (c=0; c<num_tracks; c++) { /* read each track */
fread(buf, 4, 1, fp); /* read track header */
if (memcmp(buf, "MTrk", 4)) goto err;
data = mfgetl(fp); /* length of track chunk */
midi->track[c].len = data;
midi->track[c].data = (unsigned char*)malloc(data); /* allocate memory */
if (!midi->track[c].data) goto err;
/* finally, read track data */
retsiz=fread(midi->track[c].data, 1, data, fp);
if ( retsiz != data) goto err;
}
fclose(fp);
return midi;
/* oh dear... */
err:
fclose(fp);
lmDestroyMidi(midi);
return NULL;
}
void lmDestroyMidi(MIDI *midi)
{
int c;
if (midi == midifile)
lmStopMidi();
if (midi) {
for (c=0; c<MIDI_TRACKS; c++) {
if (midi->track[c].data) {
free(midi->track[c].data);
}
}
free(midi);
}
}
static unsigned long parse_var_len(unsigned char **data)
{
unsigned long val = **data & 0x7F;
while (**data & 0x80) {
(*data)++;
val <<= 7;
val += (**data & 0x7F);
}
(*data)++;
return val;
}
/* global_volume_fix:
* Converts a note volume, adjusting it according to the global
* _midi_volume variable.
*/
static inline int global_volume_fix(int vol)
{
if (lm_midi_volume >= 0)
return (vol * lm_midi_volume) / 256;
return vol;
}
/* sort_out_volume:
* Converts a note volume, adjusting it according to the channel volume
* and the global _midi_volume variable.
*/
static inline int sort_out_volume(int c, int vol)
{
return global_volume_fix((vol * midi_channel[c].volume) / 128);
}
/* raw_program_change:
* Sends a program change message to a device capable of handling raw
* MIDI data, using patch mapping tables. Assumes that midi_driver->raw_midi
* isn't NULL, so check before calling it!
*/
static void raw_program_change(int channel, int patch)
{
if (channel != 9) {
/* bank change #1 */
if (patch_table[patch].bank1 >= 0) {
midi_driver->raw_midi(0xB0+channel);
midi_driver->raw_midi(0);
midi_driver->raw_midi(patch_table[patch].bank1);
}
/* bank change #2 */
if (patch_table[patch].bank2 >= 0) {
midi_driver->raw_midi(0xB0+channel);
midi_driver->raw_midi(32);
midi_driver->raw_midi(patch_table[patch].bank2);
}
/* program change */
midi_driver->raw_midi(0xC0+channel);
midi_driver->raw_midi(patch_table[patch].prog);
/* update volume */
midi_driver->raw_midi(0xB0+channel);
midi_driver->raw_midi(7);
midi_driver->raw_midi(global_volume_fix(midi_channel[channel].volume-1));
}
}
/* midi_note_off:
* Processes a MIDI note-off event.
*/
static void midi_note_off(int channel, int note)
{
int done = FALSE;
int voice, layer;
int c;
/* can we send raw MIDI data? */
if (midi_driver->raw_midi) {
if (channel != 9)
note += patch_table[midi_channel[channel].patch].pitch;
midi_driver->raw_midi(0x80+channel);
midi_driver->raw_midi(note);
midi_driver->raw_midi(0);
return;
}
/* oh well, have to do it the long way... */
for (layer=0; layer<MIDI_LAYERS; layer++) {
voice = midi_channel[channel].note[note][layer];
if (voice >= 0) {
midi_driver->key_off(voice + midi_driver->basevoice);
midi_voice[voice].note = -1;
midi_voice[voice].time = _midi_tick;
midi_channel[channel].note[note][layer] = -1;
done = TRUE;
}
}
/* if the note isn't playing, it must still be in the waiting room */
if (!done) {
for (c=0; c<MIDI_VOICES; c++) {
if ((midi_waiting[c].channel == channel) &&
(midi_waiting[c].note == note)) {
midi_waiting[c].note = -1;
break;
}
}
}
}
/* sort_out_pitch_bend:
* MIDI pitch bend range is + or - two semitones. The low-level soundcard
* drivers can only handle bends up to +1 semitone. This routine converts
* pitch bends from MIDI format to our own format.
*/
static inline void sort_out_pitch_bend(int *bend, int *note)
{
if (*bend == 0x2000) {
*bend = 0;
return;
}
(*bend) -= 0x2000;
while (*bend < 0) {
(*note)--;
(*bend) += 0x1000;
}
while (*bend >= 0x1000) {
(*note)++;
(*bend) -= 0x1000;
}
}
/* _midi_allocate_voice:
* Allocates a MIDI voice in the range min-max (inclusive). This is
* intended to be called by the key_on() handlers in the MIDI driver,
* and shouldn't be used by any other code.
*/
int lm_midi_allocate_voice(int min, int max)
{
int c;
int layer;
int voice = -1;
int best_time = LONG_MAX;
if (min < 0)
min = 0;
if (max < 0)
max = midi_driver->voices-1;
/* which layer can we use? */
for (layer=0; layer<MIDI_LAYERS; layer++)
if (midi_channel[midi_alloc_channel].note[midi_alloc_note][layer] < 0)
break;
if (layer >= MIDI_LAYERS)
return -1;
/* find a free voice */
for (c=min; c<=max; c++) {
if ((midi_voice[c].note < 0) &&
(midi_voice[c].time < best_time) &&
((c < midi_driver->xmin) || (c > midi_driver->xmax))) {
voice = c;
best_time = midi_voice[c].time;
}
}
/* if there are no free voices, kill a note to make room */
if (voice < 0) {
voice = -1;
best_time = LONG_MAX;
for (c=min; c<=max; c++) {
if ((midi_voice[c].time < best_time) &&
((c < midi_driver->xmin) || (c > midi_driver->xmax))) {
voice = c;
best_time = midi_voice[c].time;
}
}
if (voice >= 0)
midi_note_off(midi_voice[voice].channel, midi_voice[voice].note);
else
return -1;
}
/* ok, we got it... */
midi_voice[voice].channel = midi_alloc_channel;
midi_voice[voice].note = midi_alloc_note;
midi_voice[voice].volume = midi_alloc_vol;
midi_voice[voice].time = _midi_tick;
midi_channel[midi_alloc_channel].note[midi_alloc_note][layer] = voice;
return voice + midi_driver->basevoice;
}
/* midi_note_on:
* Processes a MIDI note-on event. Tries to find a free soundcard voice,
* and if it can't either cuts off an existing note, or if 'polite' is
* set, just stores the channel, note and volume in the waiting list.
*/
static void midi_note_on(int channel, int note, int vol, int polite)
{
int c, layer, inst, bend, corrected_note;
/* it's easy if the driver can handle raw MIDI data */
if (midi_driver->raw_midi) {
if (channel != 9)
note += patch_table[midi_channel[channel].patch].pitch;
midi_driver->raw_midi(0x90+channel);
midi_driver->raw_midi(note);
midi_driver->raw_midi(vol);
return;
}
/* if the note is already on, turn it off */
for (layer=0; layer<MIDI_LAYERS; layer++) {
if (midi_channel[channel].note[note][layer] >= 0) {
midi_note_off(channel, note);
return;
}
}
/* if zero volume and the note isn't playing, we can just ignore it */
if (vol == 0)
return;
if (channel != 9) {
/* are there any free voices? */
for (c=0; c<midi_driver->voices; c++)
if ((midi_voice[c].note < 0) &&
((c < midi_driver->xmin) || (c > midi_driver->xmax)))
break;
/* if there are no free voices, remember the note for later */
if ((c >= midi_driver->voices) && (polite)) {
for (c=0; c<MIDI_VOICES; c++) {
if (midi_waiting[c].note < 0) {
midi_waiting[c].channel = channel;
midi_waiting[c].note = note;
midi_waiting[c].volume = vol;
break;
}
}
return;
}
}
/* drum sound? */
if (channel == 9) {
inst = 128+note;
corrected_note = 60;
bend = 0;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -