mirror of
https://github.com/dimoniche/solarium.vlad.git
synced 2026-01-29 20:43:31 +03:00
401 lines
12 KiB
C
401 lines
12 KiB
C
#ifdef CONFIG_MUSIC_ENABLE
|
||
|
||
#include <includes.h>
|
||
#include "ff.h"
|
||
#include "app_serv.h"
|
||
#include "music_tsk.h"
|
||
#include "player.h"
|
||
#include "data.h"
|
||
#include "datadesc.h"
|
||
#include "sdcard.h"
|
||
|
||
static FATFS FatFs; /* FatFs work area needed for each volume */
|
||
static FIL Fil; /* File object needed for each open file */
|
||
|
||
CPU_INT32U fat_status;
|
||
CPU_INT08U music_status;
|
||
CPU_INT08U music_cansel;
|
||
|
||
#define FILE_BUFFER_SIZE 512
|
||
/* How many transferred bytes between collecting data.
|
||
A value between 1-8 KiB is typically a good value.
|
||
If REPORT_ON_SCREEN is defined, a report is given on screen each time
|
||
data is collected. */
|
||
#define REPORT_INTERVAL 4096
|
||
#define REPORT_INTERVAL_MIDI 512
|
||
|
||
static u_int8 playBuf[FILE_BUFFER_SIZE];
|
||
|
||
OS_STK MusicTaskStk[MUSIC_TASK_STK_SIZE];
|
||
OS_EVENT *MusicQuery = NULL;
|
||
void *MusicQueryTbl[MUSIC_QUERY_LEN];
|
||
|
||
///
|
||
int IsMusicPlaying(void)
|
||
{
|
||
if (music_status == MUSIC_STATUS_PLAYING) return 1;
|
||
return 0;
|
||
}
|
||
|
||
///
|
||
void MusicCanselPlay(void)
|
||
{
|
||
music_cansel = 1;
|
||
}
|
||
|
||
///
|
||
int GetMusicEvent(int* event)
|
||
{
|
||
CPU_INT08U err = 0;
|
||
int evt = (int)OSQPend(MusicQuery, 1, &err);
|
||
if (err != 0) return 0;
|
||
*event = evt;
|
||
return 1;
|
||
}
|
||
|
||
///
|
||
void PostMusicEvent(int event)
|
||
{
|
||
OSQPost(MusicQuery, (void *)event);
|
||
}
|
||
|
||
enum AudioFormat {
|
||
afUnknown,
|
||
afRiff,
|
||
afOggVorbis,
|
||
afMp1,
|
||
afMp2,
|
||
afMp3,
|
||
afAacMp4,
|
||
afAacAdts,
|
||
afAacAdif,
|
||
afFlac,
|
||
afWma,
|
||
afMidi,
|
||
} audioFormat = afUnknown;
|
||
|
||
const char *afName[] = {
|
||
"unknown",
|
||
"RIFF",
|
||
"Ogg",
|
||
"MP1",
|
||
"MP2",
|
||
"MP3",
|
||
"AAC MP4",
|
||
"AAC ADTS",
|
||
"AAC ADIF",
|
||
"FLAC",
|
||
"WMA",
|
||
"MIDI",
|
||
};
|
||
|
||
enum PlayerStates {
|
||
psPlayback = 0,
|
||
psUserRequestedCancel,
|
||
psCancelSentToVS10xx,
|
||
psStopped
|
||
} playerState;
|
||
|
||
|
||
int playFile(FIL *readFp, uint16_t volume)
|
||
{
|
||
UINT bytesInBuffer; // How many bytes in buffer left
|
||
u_int32 pos=0; // File position
|
||
int endFillByte = 0; // What byte value to send after file
|
||
int endFillBytes = 2050; // How many of those to send
|
||
long nextReportPos=0; // File pointer where to next collect/report
|
||
int i;
|
||
|
||
playerState = psPlayback; // Set state to normal playback
|
||
|
||
WriteSci(SCI_DECODE_TIME, 0); // Reset DECODE_TIME
|
||
|
||
// volume = 0..255
|
||
WriteSci(SCI_VOL, volume*0x101);
|
||
|
||
// Main playback loop
|
||
|
||
while ((f_read(readFp, playBuf, FILE_BUFFER_SIZE, &bytesInBuffer) == FR_OK) && (bytesInBuffer > 0) &&
|
||
(playerState != psStopped))
|
||
{
|
||
u_int8 *bufP = playBuf;
|
||
|
||
while (bytesInBuffer && (playerState != psStopped))
|
||
{
|
||
int t = min(SDI_MAX_TRANSFER_SIZE, bytesInBuffer);
|
||
|
||
// This is the heart of the algorithm: on the following line
|
||
// actual audio data gets sent to VS10xx.
|
||
if (WriteSdi(bufP, t) != 0) return -1;
|
||
|
||
bufP += t;
|
||
bytesInBuffer -= t;
|
||
pos += t;
|
||
|
||
// If the user has requested cancel, set VS10xx SM_CANCEL bit
|
||
if (playerState == psUserRequestedCancel)
|
||
{
|
||
unsigned short oldMode;
|
||
playerState = psCancelSentToVS10xx;
|
||
oldMode = ReadSci(SCI_MODE);
|
||
WriteSci(SCI_MODE, oldMode | SM_CANCEL);
|
||
}
|
||
|
||
// If VS10xx SM_CANCEL bit has been set, see if it has gone
|
||
// through. If it is, it is time to stop playback.
|
||
if (playerState == psCancelSentToVS10xx)
|
||
{
|
||
unsigned short mode = ReadSci(SCI_MODE);
|
||
if (!(mode & SM_CANCEL))
|
||
{
|
||
playerState = psStopped;
|
||
}
|
||
}
|
||
|
||
// If playback is going on as normal, see if we need to collect and
|
||
// possibly report
|
||
if (playerState == psPlayback && pos >= nextReportPos)
|
||
{
|
||
nextReportPos += (audioFormat == afMidi || audioFormat == afUnknown) ? REPORT_INTERVAL_MIDI : REPORT_INTERVAL;
|
||
/* It is important to collect endFillByte while still in normal
|
||
playback. If we need to later cancel playback or run into any
|
||
trouble with e.g. a broken file, we need to be able to repeatedly
|
||
send this byte until the decoder has been able to exit. */
|
||
endFillByte = ReadVS10xxMem(PAR_END_FILL_BYTE);
|
||
}
|
||
} // if (playerState == psPlayback && pos >= nextReportPos)
|
||
|
||
|
||
if ((music_cansel) &&(playerState == psPlayback))
|
||
{
|
||
playerState = psUserRequestedCancel;
|
||
}
|
||
|
||
} // while ((bytesInBuffer = fread(...)) > 0 && playerState != psStopped)
|
||
|
||
// Earlier we collected endFillByte. Now, just in case the file was
|
||
// broken, or if a cancel playback command has been given, write
|
||
// lots of endFillBytes.
|
||
memset(playBuf, endFillByte, sizeof(playBuf));
|
||
for (i=0; i<endFillBytes; i+=SDI_MAX_TRANSFER_SIZE)
|
||
{
|
||
if (WriteSdi(playBuf, SDI_MAX_TRANSFER_SIZE) != 0) return -2;
|
||
}
|
||
|
||
// If the file actually ended, and playback cancellation was not
|
||
// done earlier, do it now.
|
||
if (playerState == psPlayback)
|
||
{
|
||
unsigned short oldMode = ReadSci(SCI_MODE);
|
||
WriteSci(SCI_MODE, oldMode | SM_CANCEL);
|
||
while (ReadSci(SCI_MODE) & SM_CANCEL) WriteSdi(playBuf, 2);
|
||
}
|
||
|
||
// That's it. Now we've played the file as we should, and left VS10xx
|
||
// in a stable state. It is now safe to call this function again for
|
||
// the next song, and again, and again...
|
||
return 0;
|
||
}
|
||
|
||
/// <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
FRESULT find_file(char* dirpath, UINT index, char* filename)
|
||
{
|
||
FRESULT res;
|
||
DIR dir;
|
||
static FILINFO fno;
|
||
|
||
res = f_opendir(&dir, dirpath); // Open the directory
|
||
if (res == FR_OK)
|
||
{
|
||
char index_str[4];
|
||
sprintf(index_str, "%03d", index);
|
||
for (;;)
|
||
{
|
||
res = f_readdir(&dir, &fno); // Read a directory item
|
||
if (res != FR_OK || fno.fname[0] == 0) break; // Break on error or end of dir
|
||
if (!(fno.fattrib & AM_DIR))
|
||
{
|
||
if (strncmp(fno.fname, index_str, 3) == 0)
|
||
{
|
||
strcpy(filename, fno.fname);
|
||
f_closedir(&dir);
|
||
return FR_OK;
|
||
}
|
||
}
|
||
}
|
||
f_closedir(&dir);
|
||
res = FR_NO_FILE;
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
///
|
||
void MusicTask(void *p_arg)
|
||
{
|
||
CPU_INT32U time_stamp;
|
||
int event;
|
||
|
||
if (f_mount(&FatFs, "", 1) == FR_OK)
|
||
{
|
||
fat_status = FAT_STATUS_OK;
|
||
}
|
||
else
|
||
{
|
||
fat_status = FAT_STATUS_EMPTY;
|
||
}
|
||
|
||
time_stamp = OSTimeGet();
|
||
while (1)
|
||
{
|
||
if (fat_status == FAT_STATUS_OK)
|
||
{
|
||
CPU_INT32U sound_type;
|
||
GetData(&SoundTypeDesc, &sound_type, 0, DATA_FLAG_SYSTEM_INDEX);
|
||
if (sound_type != 1)
|
||
{
|
||
while (GetMusicEvent(&event)); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
file_menu_cmd = 0;
|
||
OSTimeDly(10000);
|
||
continue;
|
||
}
|
||
|
||
if (GetMusicEvent(&event))
|
||
{
|
||
if (event >= MUSIC_EVENT_PLAY_FILE)
|
||
{
|
||
music_cansel = 0;
|
||
if (!player_connected()) player_init();
|
||
if (player_connected())
|
||
{
|
||
char filepath[12 + 1];
|
||
if (find_file("/audio", event - MUSIC_EVENT_PLAY_FILE, filepath) == FR_OK)
|
||
{
|
||
if (f_chdir("/audio") == FR_OK)
|
||
{
|
||
music_status = MUSIC_STATUS_PLAYING;
|
||
if (f_open(&Fil, filepath, FA_READ | FA_OPEN_EXISTING) == FR_OK)
|
||
{
|
||
CPU_INT32U volume100;
|
||
uint16_t volume;
|
||
GetData(&SoundVolumeDesc, &volume100, 0, DATA_FLAG_SYSTEM_INDEX);
|
||
volume = (255 * (100 - volume100)) / 100;
|
||
playFile(&Fil, volume);
|
||
f_close(&Fil);
|
||
}
|
||
f_chdir("/");
|
||
music_status = MUSIC_STATUS_STOPPED;
|
||
}
|
||
}
|
||
}
|
||
file_menu_cmd = 0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (OSTimeGet() - time_stamp > 10000)
|
||
{
|
||
time_stamp = OSTimeGet();
|
||
if (f_open(&Fil, "fstab.tmp", FA_READ | FA_OPEN_EXISTING) == FR_OK)
|
||
{
|
||
UINT rbytes;
|
||
if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
|
||
{
|
||
f_close(&Fil);
|
||
}
|
||
else
|
||
{
|
||
f_close(&Fil);
|
||
fat_status = FAT_STATUS_EMPTY;
|
||
f_unmount("");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
fat_status = FAT_STATUS_EMPTY;
|
||
f_unmount("");
|
||
}
|
||
}
|
||
OSTimeDly(10);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
while (GetMusicEvent(&event)); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
if (OSTimeGet() - time_stamp > 10000)
|
||
{
|
||
time_stamp = OSTimeGet();
|
||
sd_init();
|
||
if (f_mount(&FatFs, "", 1) == FR_OK)
|
||
{
|
||
UINT rbytes;
|
||
if (f_open(&Fil, "fstab.tmp", FA_READ | FA_OPEN_EXISTING) == FR_OK)
|
||
{
|
||
if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
|
||
{
|
||
if (f_close(&Fil) == FR_OK)
|
||
{
|
||
fat_status = FAT_STATUS_OK;
|
||
}
|
||
}
|
||
}
|
||
if (fat_status != FAT_STATUS_OK)
|
||
{
|
||
if (f_open(&Fil, "fstab.tmp", FA_WRITE | FA_CREATE_NEW) == FR_OK)
|
||
{
|
||
if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
|
||
{
|
||
if (f_close(&Fil) == FR_OK)
|
||
{
|
||
fat_status = FAT_STATUS_OK;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
UINT wbytes;
|
||
if ((f_write(&Fil, playBuf, 10, &wbytes) == FR_OK) && (wbytes == 10))
|
||
{
|
||
if (f_close(&Fil) == FR_OK)
|
||
{
|
||
fat_status = FAT_STATUS_OK;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
f_close(&Fil);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
f_unmount("");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
fat_status = FAT_STATUS_EMPTY;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
OSTimeDly(10);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
///
|
||
void InitMusicTask(void)
|
||
{
|
||
if (MusicQuery == NULL)
|
||
{
|
||
MusicQuery = OSQCreate(&MusicQueryTbl[0], MUSIC_QUERY_LEN);
|
||
}
|
||
fat_status = FAT_STATUS_NOCONN;
|
||
music_status = MUSIC_STATUS_STOPPED;
|
||
OSTaskCreate(MusicTask, (void *)0, (OS_STK *)&MusicTaskStk[MUSIC_TASK_STK_SIZE-1], MUSIC_TASK_PRIO);
|
||
}
|
||
|
||
#endif
|