ab5a38db92
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15189 a1c6a512-1295-4272-9138-f99709370657
453 lines
12 KiB
C
453 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Stepan Moskovchenko
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "plugin.h"
|
|
#include "guspat.h"
|
|
#include "midiutil.h"
|
|
#include "synth.h"
|
|
|
|
extern struct plugin_api * rb;
|
|
|
|
void readTextBlock(int file, char * buf)
|
|
{
|
|
char c = 0;
|
|
do
|
|
{
|
|
c = readChar(file);
|
|
} while(c == '\n' || c == ' ' || c=='\t');
|
|
|
|
rb->lseek(file, -1, SEEK_CUR);
|
|
int cp = 0;
|
|
do
|
|
{
|
|
c = readChar(file);
|
|
buf[cp] = c;
|
|
cp++;
|
|
} while (c != '\n' && c != ' ' && c != '\t' && !eof(file));
|
|
buf[cp-1]=0;
|
|
rb->lseek(file, -1, SEEK_CUR);
|
|
}
|
|
|
|
/* Filename is the name of the config file */
|
|
/* The MIDI file should have been loaded at this point */
|
|
int initSynth(struct MIDIfile * mf, char * filename, char * drumConfig)
|
|
{
|
|
char patchUsed[128];
|
|
char drumUsed[128];
|
|
int a=0;
|
|
for(a=0; a<MAX_VOICES; a++)
|
|
{
|
|
voices[a].cp=0;
|
|
voices[a].vol=0;
|
|
voices[a].ch=0;
|
|
voices[a].isUsed=0;
|
|
voices[a].note=0;
|
|
}
|
|
|
|
for(a=0; a<16; a++)
|
|
{
|
|
chVol[a]=100; /* Default, not quite full blast.. */
|
|
chPan[a]=64; /* Center */
|
|
chPat[a]=0; /* Ac Gr Piano */
|
|
chPW[a]=256; /* .. not .. bent ? */
|
|
chPBDepth[a]=2; /* Default bend value is 2 */
|
|
chPBNoteOffset[a]=0; /* No offset */
|
|
chPBFractBend[a]=65536; /* Center.. no bend */
|
|
}
|
|
for(a=0; a<128; a++)
|
|
{
|
|
patchSet[a]=NULL;
|
|
drumSet[a]=NULL;
|
|
patchUsed[a]=0;
|
|
drumUsed[a]=0;
|
|
}
|
|
|
|
/*
|
|
* Always load the piano.
|
|
* Some files will assume its loaded without specifically
|
|
* issuing a Patch command... then we wonder why we can't hear anything
|
|
*/
|
|
patchUsed[0]=1;
|
|
|
|
/* Scan the file to see what needs to be loaded */
|
|
if(mf != NULL)
|
|
{
|
|
for(a=0; a<mf->numTracks; a++)
|
|
{
|
|
unsigned int ts=0;
|
|
|
|
if(mf->tracks[a] == NULL)
|
|
{
|
|
printf("NULL TRACK !!!");
|
|
rb->splash(HZ*2, "Null Track in loader.");
|
|
return -1;
|
|
}
|
|
|
|
for(ts=0; ts<mf->tracks[a]->numEvents; ts++)
|
|
{
|
|
|
|
if((getEvent(mf->tracks[a], ts)->status) == (MIDI_NOTE_ON+9))
|
|
drumUsed[getEvent(mf->tracks[a], ts)->d1]=1;
|
|
|
|
if( (getEvent(mf->tracks[a], ts)->status & 0xF0) == MIDI_PRGM)
|
|
patchUsed[getEvent(mf->tracks[a], ts)->d1]=1;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
/* Initialize the whole drum set */
|
|
for(a=0; a<128; a++)
|
|
drumUsed[a]=1;
|
|
|
|
}
|
|
|
|
int file = rb->open(filename, O_RDONLY);
|
|
if(file < 0)
|
|
{
|
|
printf("");
|
|
printf("No MIDI patchset found.");
|
|
printf("Please install the instruments.");
|
|
printf("See Rockbox page for more info.");
|
|
|
|
rb->splash(HZ*2, "No Instruments");
|
|
return -1;
|
|
}
|
|
|
|
char name[40];
|
|
char fn[40];
|
|
|
|
/* Scan our config file and load the right patches as needed */
|
|
int c = 0;
|
|
name[0] = '\0';
|
|
printf("Loading instruments");
|
|
for(a=0; a<128; a++)
|
|
{
|
|
while(readChar(file)!=' ' && !eof(file));
|
|
readTextBlock(file, name);
|
|
|
|
rb->snprintf(fn, 40, ROCKBOX_DIR "/patchset/%s.pat", name);
|
|
/* printf("\nLOADING: <%s> ", fn); */
|
|
|
|
if(patchUsed[a]==1)
|
|
{
|
|
patchSet[a]=gusload(fn);
|
|
|
|
if(patchSet[a] == NULL) /* There was an error loading it */
|
|
return -1;
|
|
}
|
|
|
|
while((c != '\n'))
|
|
c = readChar(file);
|
|
}
|
|
rb->close(file);
|
|
|
|
file = rb->open(drumConfig, O_RDONLY);
|
|
if(file < 0)
|
|
{
|
|
rb->splash(HZ*2, "Bad drum config. Did you install the patchset?");
|
|
return -1;
|
|
}
|
|
|
|
/* Scan our config file and load the drum data */
|
|
int idx=0;
|
|
char number[30];
|
|
printf("Loading drums");
|
|
while(!eof(file))
|
|
{
|
|
readTextBlock(file, number);
|
|
readTextBlock(file, name);
|
|
rb->snprintf(fn, 40, ROCKBOX_DIR "/patchset/%s.pat", name);
|
|
|
|
idx = rb->atoi(number);
|
|
if(idx == 0)
|
|
break;
|
|
|
|
if(drumUsed[idx]==1)
|
|
{
|
|
drumSet[idx]=gusload(fn);
|
|
if(drumSet[idx] == NULL) /* Error loading patch */
|
|
return -1;
|
|
}
|
|
|
|
while((c != '\n') && (c != 255) && (!eof(file)))
|
|
c = readChar(file);
|
|
}
|
|
rb->close(file);
|
|
return 0;
|
|
}
|
|
|
|
#define getSample(s,wf) ((short *)(wf)->data)[s]
|
|
|
|
void setPoint(struct SynthObject * so, int pt) ICODE_ATTR;
|
|
void setPoint(struct SynthObject * so, int pt)
|
|
{
|
|
if(so->ch==9) /* Drums, no ADSR */
|
|
{
|
|
so->curOffset = 1<<27;
|
|
so->curRate = 1;
|
|
return;
|
|
}
|
|
|
|
if(so->wf==NULL)
|
|
{
|
|
printf("Crap... null waveform...");
|
|
exit(1);
|
|
}
|
|
if(so->wf->envRate==NULL)
|
|
{
|
|
printf("Waveform has no envelope set");
|
|
exit(1);
|
|
}
|
|
|
|
so->curPoint = pt;
|
|
|
|
int r;
|
|
int rate = so->wf->envRate[pt];
|
|
|
|
r=3-((rate>>6) & 0x3); /* Some blatant Timidity code for rate conversion... */
|
|
r*=3;
|
|
r = (rate & 0x3f) << r;
|
|
|
|
/*
|
|
* Okay. This is the rate shift. Timidity defaults to 9, and sets
|
|
* it to 10 if you use the fast decay option. Slow decay sounds better
|
|
* on some files, except on some other files... you get chords that aren't
|
|
* done decaying yet.. and they dont harmonize with the next chord and it
|
|
* sounds like utter crap. Yes, even Timitidy does that. So I'm going to
|
|
* default this to 10, and maybe later have an option to set it to 9
|
|
* for longer decays.
|
|
*/
|
|
so->curRate = r<<10;
|
|
|
|
/*
|
|
* Do this here because the patches assume a 44100 sampling rate
|
|
* We've halved our sampling rate, ergo the ADSR code will be
|
|
* called half the time. Ergo, double the rate to keep stuff
|
|
* sounding right.
|
|
*
|
|
* Or just move the 1 up one line to optimize a tiny bit.
|
|
*/
|
|
/* so->curRate = so->curRate << 1; */
|
|
|
|
|
|
so->targetOffset = so->wf->envOffset[pt]<<(20);
|
|
if(pt==0)
|
|
so->curOffset = 0;
|
|
}
|
|
|
|
inline void stopVoice(struct SynthObject * so)
|
|
{
|
|
if(so->state == STATE_RAMPDOWN)
|
|
return;
|
|
so->state = STATE_RAMPDOWN;
|
|
so->decay = 0;
|
|
}
|
|
|
|
static inline void synthVoice(struct SynthObject * so, int32_t * out, unsigned int samples)
|
|
{
|
|
struct GWaveform * wf;
|
|
register int s;
|
|
register int s1;
|
|
register int s2;
|
|
|
|
register unsigned int cp_temp = so->cp;
|
|
|
|
wf = so->wf;
|
|
|
|
const int mode_mask24 = wf->mode&24;
|
|
const int mode_mask28 = wf->mode&28;
|
|
const int mode_mask_looprev = wf->mode&LOOP_REVERSE;
|
|
|
|
const unsigned int num_samples = (wf->numSamples-1) << FRACTSIZE;
|
|
|
|
const unsigned int end_loop = wf->endLoop << FRACTSIZE;
|
|
const unsigned int start_loop = wf->startLoop << FRACTSIZE;
|
|
const int diff_loop = end_loop-start_loop;
|
|
|
|
while(samples > 0)
|
|
{
|
|
samples--;
|
|
/* Is voice being ramped? */
|
|
if(so->state == STATE_RAMPDOWN)
|
|
{
|
|
if(so->decay != 0) /* Ramp has been started */
|
|
{
|
|
so->decay = so->decay / 2;
|
|
|
|
if(so->decay < 10 && so->decay > -10)
|
|
so->isUsed = 0;
|
|
|
|
s1=so->decay;
|
|
s2 = s1*chPan[so->ch];
|
|
s1 = (s1<<7) -s2;
|
|
*(out++)+=(((s1&0x7FFF80) << 9) | ((s2&0x7FFF80) >> 7));
|
|
continue;
|
|
}
|
|
} else /* OK to advance voice */
|
|
{
|
|
cp_temp += so->delta;
|
|
}
|
|
|
|
s2 = getSample((cp_temp >> FRACTSIZE)+1, wf);
|
|
|
|
/* LOOP_REVERSE|LOOP_PINGPONG = 24 */
|
|
if(mode_mask24 && so->loopState == STATE_LOOPING && (cp_temp < start_loop))
|
|
{
|
|
if(mode_mask_looprev)
|
|
{
|
|
cp_temp += diff_loop;
|
|
s2=getSample((cp_temp >> FRACTSIZE), wf);
|
|
}
|
|
else
|
|
{
|
|
so->delta = -so->delta; /* At this point cp_temp is wrong. We need to take a step */
|
|
}
|
|
}
|
|
|
|
if(mode_mask28 && (cp_temp >= end_loop))
|
|
{
|
|
so->loopState = STATE_LOOPING;
|
|
if(!mode_mask24)
|
|
{
|
|
cp_temp -= diff_loop;
|
|
s2=getSample((cp_temp >> FRACTSIZE), wf);
|
|
}
|
|
else
|
|
{
|
|
so->delta = -so->delta;
|
|
}
|
|
}
|
|
|
|
/* Have we overrun? */
|
|
if(cp_temp >= num_samples)
|
|
{
|
|
cp_temp -= so->delta;
|
|
s2 = getSample((cp_temp >> FRACTSIZE)+1, wf);
|
|
stopVoice(so);
|
|
}
|
|
|
|
/* Better, working, linear interpolation */
|
|
s1=getSample((cp_temp >> FRACTSIZE), wf);
|
|
|
|
s = s1 + ((signed)((s2 - s1) * (cp_temp & ((1<<FRACTSIZE)-1)))>>FRACTSIZE);
|
|
|
|
if(so->curRate == 0)
|
|
{
|
|
stopVoice(so);
|
|
// so->isUsed = 0;
|
|
|
|
}
|
|
|
|
if(so->ch != 9 && so->state != STATE_RAMPDOWN) /* Stupid ADSR code... and don't do ADSR for drums */
|
|
{
|
|
if(so->curOffset < so->targetOffset)
|
|
{
|
|
so->curOffset += (so->curRate);
|
|
if(so -> curOffset > so->targetOffset && so->curPoint != 2)
|
|
{
|
|
if(so->curPoint != 5)
|
|
{
|
|
setPoint(so, so->curPoint+1);
|
|
}
|
|
else
|
|
{
|
|
stopVoice(so);
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
so->curOffset -= (so->curRate);
|
|
if(so -> curOffset < so->targetOffset && so->curPoint != 2)
|
|
{
|
|
|
|
if(so->curPoint != 5)
|
|
{
|
|
setPoint(so, so->curPoint+1);
|
|
}
|
|
else
|
|
{
|
|
stopVoice(so);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if(so->curOffset < 0)
|
|
{
|
|
so->curOffset = so->targetOffset;
|
|
stopVoice(so);
|
|
}
|
|
|
|
s = (s * (so->curOffset >> 22) >> 8);
|
|
|
|
/* need to set ramp beginning */
|
|
if(so->state == STATE_RAMPDOWN && so->decay == 0)
|
|
{
|
|
so->decay = s*so->volscale>>14;
|
|
if(so->decay == 0)
|
|
so->decay = 1; /* stupid junk.. */
|
|
}
|
|
|
|
|
|
/* Scaling by channel volume and note volume is done in sequencer.c */
|
|
/* That saves us some multiplication and pointer operations */
|
|
s1=s*so->volscale>>14;
|
|
|
|
s2 = s1*chPan[so->ch];
|
|
s1 = (s1<<7) - s2;
|
|
*(out++)+=(((s1&0x7FFF80) << 9) | ((s2&0x7FFF80) >> 7));
|
|
|
|
}
|
|
|
|
so->cp=cp_temp; /* store this again */
|
|
return;
|
|
}
|
|
|
|
/* buffer to hold all the samples for the current tick, this is a hack
|
|
neccesary for coldfire targets as pcm_play_data uses the dma which cannot
|
|
access iram */
|
|
int32_t samp_buf[256] IBSS_ATTR;
|
|
|
|
/* synth num_samples samples and write them to the */
|
|
/* buffer pointed to by buf_ptr */
|
|
void synthSamples(int32_t *buf_ptr, unsigned int num_samples) ICODE_ATTR;
|
|
void synthSamples(int32_t *buf_ptr, unsigned int num_samples)
|
|
{
|
|
int i;
|
|
struct SynthObject *voicept;
|
|
|
|
rb->memset(samp_buf, 0, num_samples*4);
|
|
|
|
for(i=0; i < MAX_VOICES; i++)
|
|
{
|
|
voicept=&voices[i];
|
|
if(voicept->isUsed==1)
|
|
{
|
|
synthVoice(voicept, samp_buf, num_samples);
|
|
}
|
|
}
|
|
|
|
rb->memcpy(buf_ptr, samp_buf, num_samples*4);
|
|
|
|
/* TODO: Automatic Gain Control, anyone? */
|
|
/* Or, should this be implemented on the DSP's output volume instead? */
|
|
|
|
return; /* No more ghetto lowpass filter. Linear interpolation works well. */
|
|
}
|
|
|