rockbox/apps/plugins/midi/synth.c
Stepan Moskovchenko cd963d84ca MIDI: Allow seeking forward and backward using the left/right keys. Currently seeks in 5 second
increments, but this can be set to any amount. Also implemented a counter for playing time, which can 
pretty easily be used to determine the length of the file, in seconds, before playing it. The time 
isn't displayed anywhere right now, but all this can be useful if this thing is turned into a codec, or 
at least gets a nice UI.



git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15418 a1c6a512-1295-4272-9138-f99709370657
2007-11-03 04:09:38 +00:00

467 lines
13 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);
}
void resetControllers()
{
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 */
chLastCtrlMSB[a]=0; /* Set to pitch bend depth */
chLastCtrlLSB[a]=0; /* Set to pitch bend depth */
}
}
/* 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;
resetControllers();
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[512] 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)
{
if (num_samples > 512)
DEBUGF("num_samples is too big!\n");
else
{
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. */
}