diff options
Diffstat (limited to 'libtdemid/gusout.cpp')
-rw-r--r-- | libtdemid/gusout.cpp | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/libtdemid/gusout.cpp b/libtdemid/gusout.cpp new file mode 100644 index 000000000..96c9fb915 --- /dev/null +++ b/libtdemid/gusout.cpp @@ -0,0 +1,691 @@ +/************************************************************************** + + gusout.cpp - class GUSOut which implements support for Gravis + Ultrasound cards through a /dev/sequencer device + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libtdemid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "gusout.h" +#include "sndcard.h" +#include "midispec.h" +#include "gusvoices.h" +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <string.h> +#include <sys/param.h> +#include <stdlib.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +SEQ_USE_EXTBUF(); + +#ifdef HAVE_OSS_SUPPORT +struct pat_header +{ + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; +}; +struct sample_header +{ + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; +}; + +int get_dint(unsigned char *p) +{ + unsigned int v=0; + + for (int i=0;i<4;i++) + { + v |= (p[i] << (i*8)); + } + return (int)v; +} + +unsigned short get_word(unsigned char *p) +{ + unsigned short v=0; + + for (int i=0;i<2;i++) + v |= (*p++ << (i*8)); + return (short)v; +} + +#endif + +GUSOut::GUSOut(int d,int total) +{ + seqfd = -1; + devicetype=KMID_GUS; + device= d; + _ok=1; + + use8bit=0; + nvoices=total; + vm=new VoiceManager(nvoices); +} + +GUSOut::~GUSOut() +{ + closeDev(); + + delete vm; + if (delete_GUS_patches_directory) + { + free((char *)GUS_patches_directory); + delete_GUS_patches_directory = 0; + GUS_patches_directory="/etc"; + } +} + +void GUSOut::openDev (int sqfd) +{ + _ok=1; + seqfd = sqfd; + //vm->clearLists(); + if (seqfd==-1) + { + printfdebug("ERROR: Could not open /dev/sequencer\n"); + return; + } + +#ifdef HAVE_OSS_SUPPORT + + //seqbuf_clean(); + //ioctl(seqfd,SNDCTL_SEQ_RESET); + //ioctl(seqfd,SNDCTL_SEQ_PANIC); + + if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &device)==-1) + { + printfdebug("Error reseting gus samples. Please report\n"); + }; + use8bit=0; + totalmemory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &totalmemory); + freememory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &freememory); + +#endif + + +} + +void GUSOut::closeDev (void) +{ + if (!ok()) return; + vm->clearLists(); + //if (seqfd>=0) + // close(seqfd); + seqfd=-1; +} + +void GUSOut::initDev (void) +{ +#ifdef HAVE_OSS_SUPPORT + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + chnPatchChange(chn,0); + // chnPressure(chn,127); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,127); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } + + + for (int i = 0; i < nvoices; i++) + { + SEQ_CONTROL(device, i, SEQ_VOLMODE, VOL_METHOD_LINEAR); + SEQ_STOP_NOTE(device, i, vm->note(i), 64); + } + +#endif +} + + +int GUSOut::patch(int p) +{ + if (patchloaded[p]==1) return p; + printfdebug("Not loaded %d!\n",p); + p=0; + while ((p<256)&&(patchloaded[p]==0)) p++; + return p; +} + +void GUSOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + if (chn==PERCUSSION_CHANNEL) + { + if (patchloaded[note+128]==0) return; + else + if (patchloaded[chnpatch[chn]]==0) return; + }; + int v=vm->allocateVoice(chn,note); + int p; + if (chn==PERCUSSION_CHANNEL) + SEQ_SET_PATCH(device,v ,p=patch(note+128)) + else + SEQ_SET_PATCH(device,v ,p=map->patch(chn,chnpatch[chn])); + SEQ_BENDER(device, v, chnbender[chn]); + + SEQ_START_NOTE(device, v, note, vel); + // SEQ_CONTROL(device, v, CTL_MAIN_VOLUME, chncontroller[chn][CTL_MAIN_VOLUME]); + SEQ_CHN_PRESSURE(device, v , chnpressure[chn]); + } + + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +} + +void GUSOut::noteOff (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + { + SEQ_STOP_NOTE(device, i, note, vel); + vm->deallocateVoice(i); + } + +#ifdef GUSOUTDEBUG + printf("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void GUSOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + SEQ_KEY_PRESSURE(device, i, note,vel); +} + +void GUSOut::chnPatchChange (uchar chn, uchar patch) +{ + if (chn==PERCUSSION_CHANNEL) return; + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_SET_PATCH(device,i,map->patch(chn,patch)); + chnpatch[chn]=patch; + +} + +void GUSOut::chnPressure (uchar /*chn*/, uchar /*vel*/) +{ + /* int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CHN_PRESSURE(device, i , vel); + chnpressure[chn]=vel; + */ +} + +void GUSOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + chnbender[chn]=((int)msb<<7) | (lsb & 0x7F); + + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_BENDER(device, i, chnbender[chn]); +} + +void GUSOut::chnController (uchar chn, uchar ctl, uchar v) +{ + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + }; + + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CONTROL(device, i, ctl, v); + + chncontroller[chn][ctl]=v; +} + +void GUSOut::sysex(uchar *, ulong ) +{ + +} + +void GUSOut::setGUSPatchesDirectory(const char *dir) +{ + if ((dir==NULL)||(dir[0]==0)) return; + if (delete_GUS_patches_directory) + free((char *)GUS_patches_directory); + + GUS_patches_directory = strdup(dir); + delete_GUS_patches_directory=1; +} + +const char *GUSOut::patchName(int pgm) +{ + return GUS_voice_names[pgm]; +} + + +int GUSOut::loadPatch(int pgm) +{ +#ifdef HAVE_OSS_SUPPORT + struct pat_header header; + struct sample_header sample; + if (patchloaded[pgm]==1) + { +#ifdef GUSOUTDEBUG + printf("Trying to reload a patch. This should never happen, please report.\n"); +#endif + return 0; + } + if ((patchName(pgm)==NULL)||((patchName(pgm))[0]==0)) + { +#ifdef GUSOUTDEBUG + printf("Couldn't guess patch name for patch number %d\n",pgm); +#endif + return -1; + } + char *s=new char[strlen(GUS_patches_directory)+strlen(patchName(pgm))+10]; + if (s==NULL) return -1; + sprintf(s,"%s/%s.pat",GUS_patches_directory,patchName(pgm)); +#ifdef GUSOUTDEBUG + printf("Loading patch : %s\n",s); +#endif + struct patch_info *patch=NULL; + struct stat info; + if (stat(s, &info)==-1) + { +#ifdef GUSOUTDEBUG + printf("File %s doesn't exist\n",s); +#endif + return -1; + } + + FILE *fh=fopen(s,"rb"); + if (fh==NULL) + { +#ifdef GUSOUTDEBUG + printf("Couldn't open patch %s\n",s); +#endif + return -1; + } + + unsigned char tmp[256]; + if (fread(tmp,1,0xef,fh)!=0xef) + { + fclose(fh); +#ifdef GUSOUTDEBUG + printf("Short file ! \n"); +#endif + return -1; + } + memcpy ((char *) &header, tmp, sizeof (header)); + + if (strncmp(header.magic,"GF1PATCH110",12)!=0) + { +#ifdef GUSOUTDEBUG + printf("File %s is corrupted or it isn't a patch file\n",s); +#endif + return -1; + } + if (strncmp(header.version,"ID#000002",10)!=0) + { +#ifdef GUSOUTDEBUG + printf("File %s's version is not supported\n",s); +#endif + return -1; + } + unsigned short nWaves= *(unsigned short *)&tmp[85]; +#ifdef GUSOUTDEBUG + unsigned short masterVolume= *(unsigned short *)&tmp[87]; + printf("nWaves: %d\n",nWaves); + printf("masterVolume : %d\n",masterVolume); +#endif + + unsigned short i; + int offset=0xef; + for (i=0;i<nWaves;i++) + { + fseek(fh,offset,SEEK_SET); + + if (fread(tmp,1,sizeof(sample),fh) != sizeof(sample)) + { + fclose(fh); +#ifdef GUSOUTDEBUG + printf("Short file\n"); +#endif + return -1; + } + memcpy ((char *) &sample, tmp, sizeof (sample)); + sample.fractions = (char)tmp[7]; + sample.len = get_dint(&tmp[8]); + sample.loop_start = get_dint(&tmp[12]); + sample.loop_end = get_dint(&tmp[16]); + sample.base_freq = get_word(&tmp[20]); + sample.low_note = get_dint(&tmp[22]); + sample.high_note = get_dint(&tmp[26]); + sample.base_note = get_dint(&tmp[30]); + sample.detune = (short)get_word(&tmp[34]); + sample.panning = (unsigned char) tmp[36]; + + memcpy (sample.envelope_rate, &tmp[37], 6); + memcpy (sample.envelope_offset, &tmp[43], 6); + + sample.tremolo_sweep = (unsigned char) tmp[49]; + sample.tremolo_rate = (unsigned char) tmp[50]; + sample.tremolo_depth = (unsigned char) tmp[51]; + + sample.vibrato_sweep = (unsigned char) tmp[52]; + sample.vibrato_rate = (unsigned char) tmp[53]; + sample.vibrato_depth = (unsigned char) tmp[54]; + sample.modes = (unsigned char) tmp[55]; + sample.scale_frequency = (short)get_word(&tmp[56]); + sample.scale_factor = get_word(&tmp[58]); + + offset = offset + 96; + + patch = (struct patch_info *) malloc(sizeof (*patch) + sample.len); + if (patch == NULL) + { +#ifdef GUSOUTDEBUG + printf("Not enough memory\n"); +#endif + return -1; + } + patch->key = GUS_PATCH; + patch->device_no = device; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (fseek (fh, offset, 0) == -1) + { + fclose(fh); + return -1; + } + + if ((long)fread (patch->data, 1,sample.len,fh) != sample.len) + { +#ifdef GUSOUTDEBUG + printf ("Short file\n"); +#endif + return -1; + } + + SEQ_WRPATCH (patch, sizeof (*patch) + sample.len); + + offset = offset + sample.len; + + } + patchloaded[pgm]=1; + + fclose(fh); + free(patch); // Shouldn't this 'free' be within the 'for' loop ? + delete s; + freememory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &freememory); +#endif + return 0; +} + + +void GUSOut::setPatchesToUse(int *patchesused) +{ +#ifdef HAVE_OSS_SUPPORT + int k; + for (k=0;k<256;k++) patchloaded[k]=0; + + int patchesordered[256]; //This holds the pgm used ordered by a method which + // put first the patches more oftenly used, and then the least + // In example, if a song only uses a piano and a splash cymbal, + // This is set to : 0,188,-1,-1,-1,-1 ... + patchesLoadingOrder(patchesused,patchesordered); + + // If above line doesn't work, perhaps you could try this ? : + // for (int j=0;j<256;j++) patchesordered[j]=patchesused[j]; +#ifdef GUSOUTDEBUG + printf("Patches used : \n"); + for (k=0;k<256;k++) + { + if (patchesused[k]!=-1) printf("%d,",patchesused[k]); + } + printf("\n Patches used, sorted :\n"); + for (k=0;k<256;k++) + { + if (patchesordered[k]!=-1) printf("%d,",patchesordered[k]); + } +#endif + + int i=0; + while (patchesordered[i]!=-1) + { +#ifdef GUSOUTDEBUG + printf("Load Patch : %d\n",patchesordered[i]); +#endif + loadPatch(patchesordered[i]); + i++; + } +#endif +} + +int compare_decreasing(const void *a,const void *b) +{ + struct instr_gm + { + int used; + int pgm; + }; + instr_gm *ai=(instr_gm *)a; + instr_gm *bi=(instr_gm *)b; + return ai->used<bi->used; +} + + +void GUSOut::patchesLoadingOrder(int *patchesused,int *patchesordered) +{ + struct instr_gm + { + int used; + int pgm; + }; + + instr_gm tempmelody[128]; + instr_gm tempdrums[128]; + int i,j; + for (i=0,j=128;i<128;i++,j++) + { + tempmelody[i].used=patchesused[i]; + tempmelody[i].pgm=i; + tempdrums[i].used=patchesused[j]; + tempdrums[i].pgm=j; + } + /* SORT */ // Decreasing order (first most used patch, then less used patch) + qsort(&tempmelody[0],128,sizeof(instr_gm),compare_decreasing); + qsort(&tempdrums[0],128,sizeof(instr_gm),compare_decreasing); + + /* Once they are sorted, the result is put on patchesordered in the following + * way : If tempmelody is : M0 M1 M2 M3 ... M127 and tempdrums is : + * D0 D1 D2 D3 ... D127, the result is : + * M0 D0 M1 M2 D1 M3 M4 D2 M5 M6 D3 ... + * P0 P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 ... + */ + +#ifdef GUSOUTDEBUG + for (int k=0;k<128;k++) + { + printf("%d - %d\n",tempmelody[k].used,tempmelody[k].pgm); + } + for (int k=0;k<128;k++) + { + printf("%d : %d\n",tempdrums[k].used,tempdrums[k].pgm); + } +#endif + + i=0; + int totalmelody=0; + while ((i<128)&&(tempmelody[i].used!=0)) + { + totalmelody++; + i++; + } + i=0; + int totaldrums=0; + while ((i<128)&&(tempdrums[i].used!=0)) + { + totaldrums++; + i++; + } +#ifdef GUSOUTDEBUG + printf("Totalmelody : %d,totaldrums : %d\n",totalmelody,totaldrums); +#endif + int tgt=0; + + int tm=totalmelody; + int td=totaldrums; + int cm,cd; + cm=cd=0; + if ((tm!=0)&&(td!=0)) + { + patchesordered[0]=tempmelody[0].pgm; + patchesordered[1]=tempdrums[0].pgm; + tm--;td--; + cm++;cd++; + tgt+=2; + while ((tm>0)&&(td>0)) + { + if (((tgt-1)%3)==0) + { + patchesordered[tgt]=tempdrums[cd].pgm; + cd++; + td--; + } + else + { + patchesordered[tgt]=tempmelody[cm].pgm; + cm++; + tm--; + } + tgt++; + } + } + while (tm>0) + { + patchesordered[tgt]=tempmelody[cm].pgm; + tgt++; + cm++; + tm--; + } + while (td>0) + { + patchesordered[tgt]=tempdrums[cd].pgm; + tgt++; + cd++; + td--; + } + + // Now we put as not used (-1) the rest of the array + while (tgt<256) + { + patchesordered[tgt]=-1; + tgt++; + } +} + +//char *GUSOut::GUS_patches_directory="/mnt/dosc/gravis/patches"; +const char *GUSOut::GUS_patches_directory="/usr/share/ultrasnd"; + +int GUSOut::delete_GUS_patches_directory = 0; +/* No, this doesn't delete any file :-) it's just for internal use */ |