rockbox/apps/plugins/midi/midiplay.c
Stepan Moskovchenko 3be1ca0281 MIDI: Improve seeking (especially near the end of the file) by disabling playback during the seek. Next
step is to prevent delta value calculation during seeking, and just recompute the current deltas once 
seeking is over.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15426 a1c6a512-1295-4272-9138-f99709370657
2007-11-03 06:31:33 +00:00

396 lines
11 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 Karl Kurbjun based on midi2wav by 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"
#include "sequencer.h"
#include "midifile.h"
PLUGIN_HEADER
PLUGIN_IRAM_DECLARE
/* variable button definitions */
#if CONFIG_KEYPAD == RECORDER_PAD
#define BTN_QUIT BUTTON_OFF
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#define BTN_LEFT BUTTON_LEFT
#elif CONFIG_KEYPAD == ONDIO_PAD
#define BTN_QUIT BUTTON_OFF
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#define BTN_LEFT BUTTON_LEFT
#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define BTN_QUIT BUTTON_OFF
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#define BTN_LEFT BUTTON_LEFT
#define BTN_RC_QUIT BUTTON_RC_STOP
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
(CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define BTN_QUIT (BUTTON_SELECT | BUTTON_MENU)
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_SCROLL_FWD
#define BTN_DOWN BUTTON_SCROLL_BACK
#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
#define BTN_QUIT BUTTON_POWER
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
(CONFIG_KEYPAD == SANSA_C200_PAD)
#define BTN_QUIT BUTTON_POWER
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define BTN_QUIT BUTTON_POWER
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_UP
#define BTN_DOWN BUTTON_DOWN
#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define BTN_QUIT BUTTON_POWER
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_SCROLL_UP
#define BTN_DOWN BUTTON_SCROLL_DOWN
#elif CONFIG_KEYPAD == MROBE500_PAD
#define BTN_QUIT BUTTON_POWER
#define BTN_RIGHT BUTTON_RIGHT
#define BTN_LEFT BUTTON_LEFT
#define BTN_UP BUTTON_RC_PLAY
#define BTN_DOWN BUTTON_RC_DOWN
#endif
#undef SYNC
#ifdef SIMULATOR
#define SYNC
#endif
struct MIDIfile * mf IBSS_ATTR;
int numberOfSamples IBSS_ATTR; /* the number of samples in the current tick */
int playingTime IBSS_ATTR; /* How many seconds into the file have we been playing? */
int samplesThisSecond IBSS_ATTR; /* How many samples produced during this second so far? */
long bpm IBSS_ATTR;
int32_t gmbuf[BUF_SIZE*NBUF];
static unsigned int samples_in_buf;
int quit=0;
struct plugin_api * rb;
static int midimain(void * filename);
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
int retval = 0;
PLUGIN_IRAM_INIT(api)
rb = api;
if(parameter == NULL)
{
rb->splash(HZ*2, " Play .MID file ");
return PLUGIN_OK;
}
rb->lcd_setfont(0);
#if defined(HAVE_ADJUSTABLE_CPU_FREQ)
rb->cpu_boost(true);
#endif
printf("%s", parameter);
/* rb->splash(HZ, true, parameter); */
#ifdef RB_PROFILE
rb->profile_thread();
#endif
retval = midimain(parameter);
#ifdef RB_PROFILE
rb->profstop();
#endif
rb->pcm_play_stop();
rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
#if defined(HAVE_ADJUSTABLE_CPU_FREQ)
rb->cpu_boost(false);
#endif
rb->splash(HZ, "FINISHED PLAYING");
if(retval == -1)
return PLUGIN_ERROR;
return PLUGIN_OK;
}
bool swap=0;
bool lastswap=1;
static inline void synthbuf(void)
{
int32_t *outptr;
int i=BUF_SIZE;
#ifndef SYNC
if(lastswap==swap) return;
lastswap=swap;
outptr=(swap ? gmbuf : gmbuf+BUF_SIZE);
#else
outptr=gmbuf;
#endif
/* synth samples for as many whole ticks as we can fit in the buffer */
for(; i >= numberOfSamples; i -= numberOfSamples)
{
synthSamples((int32_t*)outptr, numberOfSamples);
outptr += numberOfSamples;
if( tick() == 0 )
quit=1;
}
/* how many samples did we write to the buffer? */
samples_in_buf = BUF_SIZE-i;
}
void get_more(unsigned char** start, size_t* size)
{
#ifndef SYNC
if(lastswap!=swap)
{
printf("Buffer miss!"); // Comment out the printf to make missses less noticable.
}
#else
synthbuf(); // For some reason midiplayer crashes when an update is forced
#endif
*size = samples_in_buf*sizeof(int32_t);
#ifndef SYNC
*start = (unsigned char*)((swap ? gmbuf : gmbuf + BUF_SIZE));
swap=!swap;
#else
*start = (unsigned char*)(gmbuf);
#endif
}
static int midimain(void * filename)
{
int notesUsed = 0;
int a=0;
printf("Loading file");
mf= loadFile(filename);
if(mf == NULL)
{
printf("Error loading file.");
return -1;
}
if (initSynth(mf, ROCKBOX_DIR "/patchset/patchset.cfg",
ROCKBOX_DIR "/patchset/drums.cfg") == -1)
return -1;
rb->pcm_play_stop();
#if INPUT_SRC_CAPS != 0
/* Select playback */
rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
#endif
rb->pcm_set_frequency(SAMPLE_RATE); // 44100 22050 11025
/*
* tick() will do one MIDI clock tick. Then, there's a loop here that
* will generate the right number of samples per MIDI tick. The whole
* MIDI playback is timed in terms of this value.. there are no forced
* delays or anything. It just produces enough samples for each tick, and
* the playback of these samples is what makes the timings right.
*
* This seems to work quite well. On a laptop, anyway.
*/
printf("Okay, starting sequencing");
bpm=mf->div*1000000/tempo;
numberOfSamples=SAMPLE_RATE/bpm;
/* Skip over any junk in the beginning of the file, so start playing */
/* after the first note event */
do
{
notesUsed = 0;
for(a=0; a<MAX_VOICES; a++)
if(voices[a].isUsed == 1)
notesUsed++;
tick();
} while(notesUsed == 0);
playingTime = 0;
samplesThisSecond = 0;
synthbuf();
rb->pcm_play_data(&get_more, NULL, 0);
int vol=0;
while(!quit)
{
#ifndef SYNC
synthbuf();
#endif
rb->yield();
/* Prevent idle poweroff */
rb->reset_poweroff_timer();
/* Code taken from Oscilloscope plugin */
switch(rb->button_get(false))
{
case BTN_UP:
case BTN_UP | BUTTON_REPEAT:
vol = rb->global_settings->volume;
if (vol < rb->sound_max(SOUND_VOLUME))
{
vol++;
rb->sound_set(SOUND_VOLUME, vol);
rb->global_settings->volume = vol;
}
break;
case BTN_DOWN:
case BTN_DOWN | BUTTON_REPEAT:
vol = rb->global_settings->volume;
if (vol > rb->sound_min(SOUND_VOLUME))
{
vol--;
rb->sound_set(SOUND_VOLUME, vol);
rb->global_settings->volume = vol;
}
break;
case BTN_LEFT:
{
/* Rewinding is tricky. Basically start the file over */
/* but run through the tracks without the synth running */
rb->pcm_play_stop();
int desiredTime = playingTime - 5; /* Rewind 5 sec */
if(desiredTime < 0)
desiredTime = 0;
/* Set controllers to default values */
resetControllers();
/* Set the tempo to defalt */
bpm=mf->div*1000000/tempo;
numberOfSamples=SAMPLE_RATE/bpm;
/* Reset the tracks to start */
rewindFile();
/* Reset the time counter to 0 */
playingTime = 0;
samplesThisSecond = 0;
/* Quickly run through any initial things that occur before notes */
do
{
notesUsed = 0;
for(a=0; a<MAX_VOICES; a++)
if(voices[a].isUsed == 1)
notesUsed++;
tick();
} while(notesUsed == 0);
/* Reset the time counter to 0 */
playingTime = 0;
samplesThisSecond = 0;
/* Tick until goal is reached */
while(playingTime < desiredTime)
tick();
rb->pcm_play_data(&get_more, NULL, 0);
break;
}
case BTN_RIGHT:
{
/* Skip 5 seconds forward */
/* Skipping forward is easy */
/* Should skip length be retrieved from the RB settings? */
int samp = 5*SAMPLE_RATE;
/* Have the issue where numberOfSamples changes within this tick */
int tickCount = samp / numberOfSamples;
int a=0;
rb->pcm_play_stop();
for(a=0; a<tickCount; a++)
tick();
rb->pcm_play_data(&get_more, NULL, 0);
break;
}
#ifdef BTN_RC_QUIT
case BTN_RC_QUIT:
#endif
case BTN_QUIT:
quit=1;
}
}
return 0;
}