/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * Open Sound System sequencer support. * * By Peter Wang. * * See readme.txt for copyright information. */ #include "allegro.h" #ifdef ALLEGRO_WITH_OSSMIDI #include "allegro/internal/aintern.h" #include "allegro/platform/aintunix.h" #include #include #include #include #if defined(HAVE_SOUNDCARD_H) #include #elif defined(HAVE_SYS_SOUNDCARD_H) #include #elif defined(HAVE_MACHINE_SOUNDCARD_H) #include #elif defined(HAVE_LINUX_SOUNDCARD_H) #include #endif #include #if defined(HAVE_LINUX_AWE_VOICE_H) #include #define HAVE_AWE32 #endif /* our patch data */ #include "../misc/fm_instr.h" #include "../misc/fm_emu.h" static int oss_midi_detect(int input); static int oss_midi_init(int input, int voices); static void oss_midi_exit(int input); static int oss_midi_mixer_volume(int volume); static void oss_midi_key_on(int inst, int note, int bend, int vol, int pan); static void oss_midi_key_off(int voice); static void oss_midi_set_volume(int voice, int vol); static void oss_midi_set_pitch(int voice, int note, int bend); #define MAX_VOICES 256 static int seq_fd = -1; static int seq_device; static int seq_synth_type, seq_synth_subtype; static int seq_patch[MAX_VOICES]; static int seq_note[MAX_VOICES]; static int seq_drum_start; static char seq_desc[256] = EMPTY_STRING; static char seq_driver[256] = EMPTY_STRING; static char mixer_driver[256] = EMPTY_STRING; SEQ_DEFINEBUF(2048); MIDI_DRIVER midi_oss = { MIDI_OSS, empty_string, empty_string, "Open Sound System", 0, 0, 0xFFFF, 0, -1, -1, oss_midi_detect, oss_midi_init, oss_midi_exit, oss_midi_mixer_volume, NULL, _dummy_load_patches, _dummy_adjust_patches, oss_midi_key_on, oss_midi_key_off, oss_midi_set_volume, oss_midi_set_pitch, _dummy_noop2, /* TODO */ _dummy_noop2 /* TODO */ }; /* as required by the OSS API */ void seqbuf_dump(void) { if (_seqbufptr) { write(seq_fd, _seqbuf, _seqbufptr); _seqbufptr = 0; } } /* attempt to open sequencer device */ static int seq_attempt_open(void) { char tmp1[128], tmp2[128], tmp3[128]; int fd; ustrzcpy(seq_driver, sizeof(seq_driver), get_config_string(uconvert_ascii("sound", tmp1), uconvert_ascii("oss_midi_driver", tmp2), uconvert_ascii("/dev/sequencer", tmp3))); fd = open(uconvert_toascii(seq_driver, tmp1), O_WRONLY); if (fd < 0) uszprintf(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("%s: %s"), seq_driver, ustrerror(errno)); return fd; } /* find the best (supported) synth type for device */ static int seq_find_synth(int fd) { struct synth_info info; int num_synths, i; char *s; char tmp1[64], tmp2[256]; int score = 0, best_score, best_device; if (ioctl(fd, SNDCTL_SEQ_NRSYNTHS, &num_synths) == -1) return 0; best_device = -1; best_score = 0; /* Detect the best device */ for (i = 0; i < num_synths; i++) { info.device = i; if (ioctl(fd, SNDCTL_SYNTH_INFO, &info) == -1) return 0; switch (info.synth_type) { case SYNTH_TYPE_FM: /* FM synthesis is kind of ok */ score = 2; break; case SYNTH_TYPE_SAMPLE: /* Wavetable MIDI is cool! */ score = 3; break; case SYNTH_TYPE_MIDI: /* Only weird people want to use the MIDI out port... */ /* ... so we don't accept it yet, this can be fixed when * we've got a config file option to select the MIDI device * so then we'll only select this if people specifically * ask for it */ score = 0; break; } if (score > best_score) { best_score = score; best_device = i; } } if (best_score == 0) { return 0; } /* Cool, we got a decent synth type */ seq_device = best_device; /* Now get more information */ info.device = seq_device; if (ioctl(fd, SNDCTL_SYNTH_INFO, &info) == -1) return 0; seq_synth_type = info.synth_type; seq_synth_subtype = info.synth_subtype; midi_oss.voices = info.nr_voices; if (midi_oss.voices > MAX_VOICES) midi_oss.voices = MAX_VOICES; switch (seq_synth_type) { case SYNTH_TYPE_FM: switch (seq_synth_subtype) { case FM_TYPE_ADLIB: s = uconvert_ascii("Adlib", tmp1); break; case FM_TYPE_OPL3: s = uconvert_ascii("OPL3", tmp1); break; default: s = uconvert_ascii("FM (unknown)", tmp1); break; } break; case SYNTH_TYPE_SAMPLE: switch (seq_synth_subtype) { #ifdef HAVE_AWE32 case SAMPLE_TYPE_AWE32: s = uconvert_ascii("AWE32", tmp1); break; #endif default: s = uconvert_ascii("sample (unknown)", tmp1); break; } break; case SYNTH_TYPE_MIDI: s = uconvert_ascii("MIDI out", tmp1); break; default: s = uconvert_ascii("Unknown synth", tmp1); break; } uszprintf(seq_desc, sizeof(seq_desc), uconvert_ascii("Open Sound System (%s)", tmp2), s); midi_driver->desc = seq_desc; return 1; } /* FM synth: load our instrument patches */ static void seq_set_fm_patches(int fd) { struct sbi_instrument ins; int i; ins.device = seq_device; ins.key = FM_PATCH; memset(ins.operators, 0, sizeof(ins.operators)); /* instruments */ for (i=0; i<128; i++) { ins.channel = i; memcpy(&ins.operators, &fm_instrument[i], sizeof(FM_INSTRUMENT)); write(fd, &ins, sizeof(ins)); } /* (emulated) drums */ for (i=0; i<47; i++) { ins.channel = 128+i; memset(ins.operators, 0, sizeof(ins.operators)); memcpy(&ins.operators, &fm_emulated_drum[i], sizeof(FM_INSTRUMENT)); write(fd, &ins, sizeof(ins)); } } /* FM synth setup */ static void seq_setup_fm (void) { seq_set_fm_patches(seq_fd); seq_drum_start = midi_oss.voices - 5; } #ifdef HAVE_AWE32 /* AWE32 synth setup */ static void seq_setup_awe32 (void) { int bits = 0, drums; seq_drum_start = midi_oss.voices; if (seq_drum_start > 32) seq_drum_start = 32; /* These non-32 cases probably never happen, since the AWE32 has * 32 voices and anything higher needs a new interface... */ if (midi_oss.voices <= 1) { drums = 0; } else if (midi_oss.voices <= 4) { drums = 1; } else if (midi_oss.voices <= 32) { drums = midi_oss.voices / 8; } else { drums = 4; } #if 0 // I think I got this wrong /* Set the top 'drums' bits of bitfield and decrement seq_drum_start */ while (drums--) bits |= (1 << --seq_drum_start); #else /* The AWE driver seems to like receiving all percussion on MIDI channel * 10 (OSS channel 9) */ bits = (1<<9); seq_drum_start -= drums; #endif /* Tell the AWE which channels are drum channels. No, I don't know * what 'multi' mode is or how to play drums in the other modes. * This is just what playmidi does (except I'm using AWE_PLAY_MULTI * instead of its value, 1). */ AWE_SET_CHANNEL_MODE(seq_device, AWE_PLAY_MULTI); AWE_DRUM_CHANNELS(seq_device, bits); } #endif /* oss_midi_detect: * Sequencer detection routine. */ static int oss_midi_detect(int input) { if (input) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Input is not supported")); return FALSE; } seq_fd = seq_attempt_open(); if (seq_fd < 0) return FALSE; close(seq_fd); return TRUE; } /* oss_midi_init: * Init the driver. */ static int oss_midi_init(int input, int voices) { char tmp1[128], tmp2[128], tmp3[128]; unsigned int i; if (input) { ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Input is not supported")); return -1; } seq_fd = seq_attempt_open(); if (seq_fd < 0) return -1; if (!seq_find_synth(seq_fd)) { close(seq_fd); ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("No supported synth type found")); return -1; } ioctl(seq_fd, SNDCTL_SEQ_RESET); /* Driver-specific setup */ if (seq_synth_type == SYNTH_TYPE_FM) { seq_setup_fm(); } else if (seq_synth_type == SYNTH_TYPE_SAMPLE) { #ifdef HAVE_AWE32 if (seq_synth_subtype == SAMPLE_TYPE_AWE32) { seq_setup_awe32(); } #endif } for (i = 0; i < (sizeof(seq_patch) / sizeof(int)); i++) { seq_patch[i] = -1; seq_note[i] = -1; } /* for the mixer routine */ ustrzcpy(mixer_driver, sizeof(mixer_driver), get_config_string(uconvert_ascii("sound", tmp1), uconvert_ascii("oss_mixer_driver", tmp2), uconvert_ascii("/dev/mixer", tmp3))); return 0; } /* oss_midi_mixer_volume: * Sets the mixer volume for output. */ static int oss_midi_mixer_volume(int volume) { int fd, vol, ret; char tmp[128]; fd = open(uconvert_toascii(mixer_driver, tmp), O_WRONLY); if (fd < 0) return -1; vol = (volume * 100) / 255; vol = (vol << 8) | (vol); ret = ioctl(fd, MIXER_WRITE(SOUND_MIXER_SYNTH), &vol); close(fd); return ((ret == -1) ? -1 : 0); } /* get_hardware_voice: * Get the hardware voice corresponding to this virtual voice. The * hardware may support more than one note per voice, in which case * you don't want to terminate old notes on reusing a voice, but when * Allegro reuses a voice it forgets the old note and won't ever send * a keyoff. So we should use more virtual (Allegro) voices than the * channels the hardware provides, and map them down to actual * hardware channels here. * * We also swap voices 9 and 15 (MIDI 10 and 16). This is necessary * because _midi_allocate_voice can only allocate in continuous ranges * -- so we use voices [0,seq_drum_start) for melody and for the * percussion [seq_drum_start,max_voices), as far as Allegro is * concerned. But hardware likes percussion on MIDI 10, so we need to * remap it. */ static int get_hardware_voice (int voice) { int hwvoice = voice; /* FIXME: is this OK/useful for other things than AWE32? */ if (seq_synth_type != SYNTH_TYPE_FM) { /* map drums >= 15, everything else < 15 */ hwvoice = hwvoice * 15 / seq_drum_start; /* fix up so drums are on MIDI channel 10 */ if (hwvoice >= 15) hwvoice = 9; else if (hwvoice == 9) hwvoice = 15; } return hwvoice; } /* oss_midi_key_on: * Triggers the specified voice. */ static void oss_midi_key_on(int inst, int note, int bend, int vol, int pan) { int voice, hwvoice; int is_percussion = 0; /* percussion? */ if (inst > 127) { voice = _midi_allocate_voice(seq_drum_start, midi_driver->voices-1); /* TODO: Peter's code decrements inst by 35; but the AWE driver * ignores inst completely, using note instead, as God (well, GM) * intended. Does the FM driver ignore note? If so then we're OK. */ note = inst-128; inst -= 35; is_percussion = 1; } else voice = _midi_allocate_voice(0, seq_drum_start-1); if (voice < 0) return; /* Get the hardware voice corresponding to this virtual voice. */ hwvoice = get_hardware_voice (voice); /* FIXME: should we do this or not for FM? */ if (seq_synth_type != SYNTH_TYPE_FM) { /* Stop any previous note on this voice -- but not if it's percussion */ if (!is_percussion && seq_note[voice] != -1) { SEQ_STOP_NOTE(seq_device, hwvoice, seq_note[voice], 64); } } seq_note[voice] = note; /* make sure the (hardware) voice is set up with the right sound */ if (inst != seq_patch[hwvoice]) { SEQ_SET_PATCH(seq_device, hwvoice, inst); seq_patch[hwvoice] = inst; } SEQ_CONTROL(seq_device, hwvoice, CTL_PAN, pan); SEQ_BENDER(seq_device, hwvoice, 8192 + bend); SEQ_START_NOTE(seq_device, hwvoice, note, vol); SEQ_DUMPBUF(); } /* oss_midi_key_off: * Insert witty remark about this line being unhelpful. */ static void oss_midi_key_off(int voice) { /* Get the hardware voice corresponding to this virtual voice. */ int hwvoice = get_hardware_voice (voice); SEQ_STOP_NOTE(seq_device, hwvoice, seq_note[voice], 64); SEQ_DUMPBUF(); seq_note[voice] = -1; } /* oss_midi_set_volume: * Sets the volume of the specified voice. */ static void oss_midi_set_volume(int voice, int vol) { SEQ_CONTROL(seq_device, voice, CTL_MAIN_VOLUME, vol); } /* oss_midi_set_pitch: * Sets the pitch of the specified voice. */ static void oss_midi_set_pitch(int voice, int note, int bend) { SEQ_CONTROL(seq_device, voice, CTRL_PITCH_BENDER, 8192 + bend); } /* oss_midi_exit: * Cleanup when we are finished. */ static void oss_midi_exit(int input) { if (input) return; if (seq_fd > 0) { close(seq_fd); seq_fd = -1; } } #endif