/************************************************************************** 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 ***************************************************************************/ #include "gusout.h" #include "sndcard.h" #include "midispec.h" #include "gusvoices.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #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;ikey = 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->usedused; } 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 */