Files
2021-12-17 22:55:34 -05:00

676 lines
18 KiB
HolyC

#define IMG_MAIN player.images[0]
#define IMG_CBUTTONS player.images[1]
#define IMG_TEXT player.images[2]
#define IMG_NUMBERS player.images[3]
#define IMG_PLAYPAUS player.images[4]
#define IMG_MONOSTER player.images[5]
#define IMG_POSBAR player.images[6]
#define BTN_NULL 0
#define BTN_PREV 1
#define BTN_PLAY 2
#define BTN_PAUSE 3
#define BTN_STOP 4
#define BTN_NEXT 5
#define BTN_EJECT 6
#define ACTION_NULL 0
#define ACTION_PICK_FILE 1
#define SRC_TYPE_NULL 0
#define SRC_TYPE_PCM 1
#define SRC_TYPE_MP3 2
I64 bitrates[16] = {0, 32, 40, 48, 56, 64, 80, 96,
112, 128, 160, 192, 224, 256, 320, 0};
I64 samplerates[4] = {44, 48, 32, 0};
class @drmp3_config {
U32 outputChannels;
U32 outputSampleRate;
};
class @id3v1 {
U8 header[3];
U8 title[30];
U8 artist[30];
U8 album[30];
U8 year[4];
};
class @id3v2 {
U8 header[3];
U16 version;
U8 flags;
U32 size;
};
class @media {
U8 title[256];
U8 artist[256];
U8 album[256];
U8 year[16];
U32 channels;
U32 sampleRate;
U8 *source_data;
I64 source_len;
I64 source_type;
I64 source_bit_rate;
I64 source_sample_rate;
U8 *pcm_data;
I64 pcm_frames;
I64 fifo_frame;
I64 seek_frame;
I64 seek_to_frame;
I64 runtime_mins;
I64 runtime_secs;
Bool paused;
Bool stopped;
Bool loading;
Bool seeking;
};
class @marquee_char {
@marquee_char *next;
U8 char;
};
class @marquee {
@marquee_char *text;
@marquee_char *char;
I64 ticks;
};
class @player {
@media media;
CTask *windowTask;
CDC **images;
@marquee marquee;
I64 pressing_button;
I64 prev_lb_state;
I64 prev_rb_state;
U64 decoder_addr;
I64 audio_stream_id;
};
@player player;
U8 *TEXT_5X6_ROW_1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\"@";
U8 *TEXT_5X6_ROW_2 = "0123456789 :()-'!_+\\/[]^&%.=\d#";
U8 *TEXT_5X6_ROW_3 = " ?*";
U8 *TEXT_5X6_ROW_4 = "abcdefghijklmnopqrstuvwxyz";
U0 Draw5x6TextChar(CDC *dc, I64 x, I64 y, U8 ch) {
if (ch == ' ')
return;
CDC *char_dc = DCNew(5, 6);
U8 substr[2];
substr[0] = ch;
substr[1] = NULL;
Bool put_char = FALSE;
if (StrFind(substr, TEXT_5X6_ROW_1)) {
GrBlot(char_dc, -(5 * (StrFind(substr, TEXT_5X6_ROW_1) - TEXT_5X6_ROW_1)),
0, IMG_TEXT);
put_char = TRUE;
}
if (StrFind(substr, TEXT_5X6_ROW_2)) {
GrBlot(char_dc, -(5 * (StrFind(substr, TEXT_5X6_ROW_2) - TEXT_5X6_ROW_2)),
-6, IMG_TEXT);
put_char = TRUE;
}
if (StrFind(substr, TEXT_5X6_ROW_3)) {
GrBlot(char_dc, -(5 * (StrFind(substr, TEXT_5X6_ROW_3) - TEXT_5X6_ROW_3)),
-12, IMG_TEXT);
put_char = TRUE;
}
if (StrFind(substr, TEXT_5X6_ROW_4)) {
GrBlot(char_dc, -(5 * (StrFind(substr, TEXT_5X6_ROW_4) - TEXT_5X6_ROW_4)),
0, IMG_TEXT);
put_char = TRUE;
}
if (put_char)
GrBlot(dc, x, y, char_dc);
DCDel(char_dc);
}
U0 Draw5x6Text(CDC *dc, I64 x, I64 y, U8 *str) {
while (*str) {
Draw5x6TextChar(dc, x, y, *str);
str++;
x += 5;
}
}
U0 DrawPlayerBitRateSampleRate(CDC *dc, I64 x, I64 y) {
if (!player.media.source_bit_rate || !player.media.source_sample_rate)
return;
U8 buf[16];
StrPrint(buf, "%03d", player.media.source_bit_rate);
if (buf[0] == '0')
buf[0] = ' ';
Draw5x6Text(dc, x, y, buf);
StrPrint(buf, "%02d", player.media.source_sample_rate);
Draw5x6Text(dc, x + 46, y, buf);
}
U0 DrawPlayerMarqueeText(CDC *dc, I64 x, I64 y) {
I64 pos = 0;
I64 posX = 0;
@marquee_char *mchar = player.marquee.char;
while (pos < 31) {
Draw5x6TextChar(dc, posX + x, y, mchar->char);
posX += 5;
mchar = mchar->next;
pos++;
}
}
Bool CheckIfButtonIsHovered(I64 x, I64 y, I64 w, I64 h) {
if (ms.pos.x >= player.windowTask->pix_left + x &&
ms.pos.x <= player.windowTask->pix_left + x + w &&
ms.pos.y >= player.windowTask->pix_top + y &&
ms.pos.y <= player.windowTask->pix_top + y + h)
return TRUE;
return FALSE;
}
U0 DrawPlayerPositionBar(CDC *dc, I64 x, I64 y) {
if (!player.media.source_data || !player.media.source_len)
return;
CDC *posbar_dc = DCNew(28, 10);
F64 i;
if (!player.media.seeking) {
GrBlot(posbar_dc, -250, 0, IMG_POSBAR);
i = (220.0 / ToF64(player.media.pcm_frames));
GrBlot(dc,
x + (i * (player.media.seek_frame +
Audio.output_frames[player.audio_stream_id])),
y, posbar_dc);
}
if (player.media.seeking) {
GrBlot(posbar_dc, -278, 0, IMG_POSBAR);
I64 j = ms.pos.x - (player.windowTask->pix_left + x) - 14;
I64 k = 0;
if (j < 0)
j = 0;
if (j > 220)
j = 220;
GrBlot(dc, x + j, y, posbar_dc);
i = (220.0 / ToF64(player.media.pcm_frames));
player.media.seek_to_frame = 0;
while ((i * (player.media.seek_to_frame +
Audio.output_frames[player.audio_stream_id]) <
j))
player.media.seek_to_frame++;
}
DCDel(posbar_dc);
}
U0 SeekTo(I64 frame) {
player.media.seek_frame = frame;
Audio.output_frames[player.audio_stream_id] = 0;
player.media.fifo_frame = 0;
}
U0 DrawPlayerControlsActive(CDC *dc, I64 x, I64 y) {
CDC *control_dc = DCNew(23, 18);
if (!ms.lb && player.prev_lb_state) {
switch (player.pressing_button) {
case BTN_PREV:
if (CheckIfButtonIsHovered(x, y, 23, 18)) {
// player.Prev();
}
break;
case BTN_PLAY:
if (CheckIfButtonIsHovered(x + (23 * 1), y, 23, 18)) {
player.media.paused = FALSE;
player.media.stopped = FALSE;
}
break;
case BTN_PAUSE:
if (CheckIfButtonIsHovered(x + (23 * 2), y, 23, 18)) {
player.media.paused = !player.media.paused;
}
break;
case BTN_STOP:
if (CheckIfButtonIsHovered(x + (23 * 3), y, 23, 18)) {
player.media.stopped = TRUE;
}
break;
case BTN_NEXT:
if (CheckIfButtonIsHovered(x + (23 * 4), y, 23, 18)) {
// player.Next();
}
break;
case BTN_EJECT:
if (CheckIfButtonIsHovered(x + 121, y + 1, 22, 15)) {
player.windowTask->user_data = ACTION_PICK_FILE;
}
default:
break;
}
player.pressing_button = BTN_NULL;
if (player.media.seeking && player.media.seek_to_frame)
SeekTo(player.media.seek_to_frame);
player.media.seeking = FALSE;
}
if (ms.lb && !player.prev_lb_state) {
if (CheckIfButtonIsHovered(x, y, 23, 18))
player.pressing_button = BTN_PREV;
if (CheckIfButtonIsHovered(x + (23 * 1), y, 23, 18))
player.pressing_button = BTN_PLAY;
if (CheckIfButtonIsHovered(x + (23 * 2), y, 23, 18))
player.pressing_button = BTN_PAUSE;
if (CheckIfButtonIsHovered(x + (23 * 3), y, 23, 18))
player.pressing_button = BTN_STOP;
if (CheckIfButtonIsHovered(x + (23 * 4), y, 23, 18))
player.pressing_button = BTN_NEXT;
if (CheckIfButtonIsHovered(x + 121, y + 1, 22, 15))
player.pressing_button = BTN_EJECT;
if (CheckIfButtonIsHovered(21, 74, 248, 10))
player.media.seeking = TRUE;
}
switch (player.pressing_button) {
case BTN_PREV:
if (CheckIfButtonIsHovered(x, y, 23, 18)) {
DCFill(control_dc);
GrBlot(control_dc, 0, -17, IMG_CBUTTONS);
GrBlot(dc, x, y, control_dc);
}
break;
case BTN_PLAY:
if (CheckIfButtonIsHovered(x + (23 * 1), y, 23, 18)) {
DCFill(control_dc);
GrBlot(control_dc, -(23 * 1), -17, IMG_CBUTTONS);
GrBlot(dc, x + (23 * 1), y, control_dc);
}
break;
case BTN_PAUSE:
if (CheckIfButtonIsHovered(x + (23 * 2), y, 23, 18)) {
DCFill(control_dc);
GrBlot(control_dc, -(23 * 2), -17, IMG_CBUTTONS);
GrBlot(dc, x + (23 * 2), y, control_dc);
}
break;
case BTN_STOP:
if (CheckIfButtonIsHovered(x + (23 * 3), y, 23, 18)) {
DCFill(control_dc);
GrBlot(control_dc, -(23 * 3), -17, IMG_CBUTTONS);
GrBlot(dc, x + (23 * 3), y, control_dc);
}
break;
case BTN_NEXT:
if (CheckIfButtonIsHovered(x + (23 * 4), y, 23, 18)) {
DCFill(control_dc);
GrBlot(control_dc, -(23 * 4), -17, IMG_CBUTTONS);
GrBlot(dc, x + (23 * 4), y, control_dc);
}
break;
case BTN_EJECT:
if (CheckIfButtonIsHovered(x + 121, y + 1, 22, 15)) {
DCFill(control_dc);
GrBlot(control_dc, -113, -16, IMG_CBUTTONS);
control_dc->color = TRANSPARENT;
GrRect(control_dc, 0, 16, 32, 32);
GrBlot(dc, x + 121, y + 1, control_dc);
}
break;
default:
break;
}
player.prev_lb_state = ms.lb;
player.prev_rb_state = ms.rb;
DCDel(control_dc);
}
U0 DrawPlayerControlsIdle(CDC *dc, I64 x, I64 y) {
CDC *controls_dc = DCNew(114, 17);
CDC *eject_dc = DCNew(22, 15);
GrBlot(controls_dc, 0, 0, IMG_CBUTTONS);
GrBlot(eject_dc, -113, -1, IMG_CBUTTONS);
GrBlot(dc, x, y, controls_dc);
GrBlot(dc, x + 121, y + 1, eject_dc);
DCDel(controls_dc);
DCDel(eject_dc);
}
U0 DrawPlayerMonoStereo(CDC *dc, I64 x, I64 y) {
CDC *mono_dc = DCNew(29, 12);
CDC *stereo_dc = DCNew(29, 12);
DCFill(mono_dc);
DCFill(stereo_dc);
switch (player.media.channels) {
case 1:
GrBlot(mono_dc, -29, 0, IMG_MONOSTER);
GrBlot(stereo_dc, 0, -12, IMG_MONOSTER);
break;
case 2:
GrBlot(mono_dc, -29, -12, IMG_MONOSTER);
GrBlot(stereo_dc, 0, 0, IMG_MONOSTER);
break;
default:
GrBlot(mono_dc, -29, -12, IMG_MONOSTER);
GrBlot(stereo_dc, 0, -12, IMG_MONOSTER);
break;
}
GrBlot(dc, x, y, mono_dc);
GrBlot(dc, x + 29, y, stereo_dc);
DCDel(mono_dc);
DCDel(stereo_dc);
}
U0 InsertMarqueeTextChar(U8 ch) {
@marquee_char *pos = player.marquee.text;
if (!player.marquee.text) {
player.marquee.text = CAlloc(sizeof(@marquee_char));
player.marquee.text->char = ch;
player.marquee.text->next = player.marquee.text;
return;
}
while (pos->next != player.marquee.text)
pos = pos->next;
pos->next = CAlloc(sizeof(@marquee_char));
pos->next->char = ch;
pos->next->next = player.marquee.text;
}
U0 SetPlayerMarqueeText(U8 *str) {
player.marquee.text = NULL;
while (*str) {
InsertMarqueeTextChar(*str);
str++;
}
player.marquee.char = player.marquee.text;
}
U0 UpdatePlayerMarqueeTextPos() {
player.marquee.ticks++;
if (player.marquee.ticks > 6) {
player.marquee.ticks = 0;
player.marquee.char = player.marquee.char->next;
}
}
@drmp3_config config;
U0 DecodeMP3Frames() {
*GCC_FUN_ADDR(U64 *) = player.decoder_addr;
config.outputChannels = 2;
config.outputSampleRate = 48000;
*HOLYC_ARG1(U64 *) = player.media.source_data;
*HOLYC_ARG2(I32 *) = player.media.source_len;
*HOLYC_ARG3(U64 *) = &config;
*HOLYC_ARG4(U64 *) = &player.media.pcm_frames;
*HOLYC_ARG5(U64 *) = NULL;
asm {
MOV RDI, [HOLYC_ARG1]
MOV RSI, [HOLYC_ARG2]
MOV RDX, [HOLYC_ARG3]
MOV RCX, [HOLYC_ARG4]
MOV R8, [HOLYC_ARG5]
MOV RAX, [GCC_FUN_ADDR]
CLI
CALL RAX
MOV [HOLYC_ARG1], RAX
STI
}
player.media.channels = config.outputChannels;
player.media.sampleRate = config.outputSampleRate;
player.media.pcm_data = *HOLYC_ARG1(U64 *);
}
U0 CalculateMediaRunTime() {
player.media.runtime_mins =
(player.media.pcm_frames / player.media.sampleRate) / 60;
player.media.runtime_secs =
(player.media.pcm_frames / player.media.sampleRate) % 60;
}
U0 ParseID3(@id3v1 *tag) {
MemSet(player.media.title, NULL, 64);
MemSet(player.media.artist, NULL, 64);
MemSet(player.media.album, NULL, 64);
MemSet(player.media.year, NULL, 16);
if (MemCmp(tag->header, "TAG", 3))
return;
MemCpy(player.media.title, tag->title, 30);
MemCpy(&player.media.artist, tag->artist, 30);
MemCpy(&player.media.album, tag->album, 30);
MemCpy(&player.media.year, tag->year, 4);
}
U0 PlayerTask() {
I64 i;
while (1) {
i = 0;
if (FifoI64Cnt(Audio.output[player.audio_stream_id].data) < BDL_BUF_SIZE &&
player.media.pcm_data && !player.media.paused && !player.media.stopped)
while (player.media.seek_frame + player.media.fifo_frame <
player.media.pcm_frames &&
i < BDL_BUF_SIZE) {
FifoI64Ins(Audio.output[player.audio_stream_id].data,
(player.media.pcm_data)(U32 *)[player.media.seek_frame +
player.media.fifo_frame]);
player.media.fifo_frame++;
i++;
}
Sleep(1);
}
}
U0 GetMP3BitRateAndSampleRate() {
I64 i = 0;
@id3v2 *tag = player.media.source_data;
if (!MemCmp(player.media.source_data, "ID3", 3)) {
i += tag->size - 10;
if (tag->flags & 1 << 4)
i -= 10;
}
while (i < player.media.source_len) {
if (player.media.source_data[i] == 0xFF &&
player.media.source_data[i + 1] & 0xE0) // Frame Sync
if (player.media.source_data[i + 1] & 0x1A) // MPEG 1, Layer 3
{
player.media.source_bit_rate =
bitrates[player.media.source_data[i + 2] >> 4 & 0xF];
player.media.source_sample_rate =
samplerates[player.media.source_data[i + 2] >> 2 & 0x3];
return;
}
i++;
Sleep(1);
}
}
U0 OpenAndPlayFile(U8 *filename) {
if (!FileFind(filename)) {
PopUpOk("Error: File not found.");
return;
}
player.media.loading = TRUE;
player.media.source_data = FileRead(filename, &player.media.source_len);
player.media.source_type = SRC_TYPE_MP3;
// I shouldn't have to do this, but I get weird memory access/corruption
// issues if I pass the memory location to ParseID3() if one of the fields is
// over 20 characters... :/
U8 id3_buffer[128];
MemCpy(id3_buffer, player.media.source_data + player.media.source_len - 128,
128);
switch (player.media.source_type) {
case SRC_TYPE_MP3:
WinFocus(player.windowTask);
Sleep(100); // Give WinMgr some time to remove the PopUpPickFile window
ParseID3(id3_buffer);
GetMP3BitRateAndSampleRate;
DecodeMP3Frames;
CalculateMediaRunTime;
break;
default:
PopUpOk("Error: Unsupported media type.");
player.media.loading = FALSE;
return;
break;
}
U8 marquee_buf[32];
StrPrint(marquee_buf, "1. %s - %s (%02d:%02d) *** ", player.media.artist,
player.media.title, player.media.runtime_mins,
player.media.runtime_secs);
SetPlayerMarqueeText(marquee_buf);
FifoI64Flush(Audio.output[player.audio_stream_id]);
player.media.fifo_frame = 0;
player.media.seek_frame = 0;
Audio.output_frames[player.audio_stream_id] = 0;
player.media.loading = FALSE;
player.media.stopped = FALSE;
player.media.paused = FALSE;
}
U0 DrawPlayerTimeElapsed(CDC *dc) {
if (player.media.stopped)
return;
if (player.media.paused && !Blink(.5))
return;
I64 i;
U8 min_buf[3];
U8 sec_buf[3];
min_buf[2] = NULL;
sec_buf[2] = NULL;
StrPrint(
min_buf, "%02d",
((player.media.seek_frame + Audio.output_frames[player.audio_stream_id]) /
player.media.sampleRate) /
60);
StrPrint(
sec_buf, "%02d",
((player.media.seek_frame + Audio.output_frames[player.audio_stream_id]) /
player.media.sampleRate) %
60);
CDC *num_dc = DCNew(9, 13);
for (i = 0; i < 2; i++) {
DCFill(num_dc);
GrBlot(num_dc, -(9 * (min_buf[i] - '0')), 0, IMG_NUMBERS);
GrBlot(dc, 50 + (12 * i), 27, num_dc);
}
for (i = 0; i < 2; i++) {
DCFill(num_dc);
GrBlot(num_dc, -(9 * (sec_buf[i] - '0')), 0, IMG_NUMBERS);
GrBlot(dc, 81 + (12 * i), 27, num_dc);
}
DCDel(num_dc);
}
U0 DrawPlayerPlayPauseStopped(CDC *dc) {
CDC *state_dc = DCNew(9, 9);
if (!player.media.paused && !player.media.stopped) {
DCFill(state_dc);
GrBlot(state_dc, 0, 0, IMG_PLAYPAUS);
GrBlot(dc, 28, 28, state_dc);
}
if (player.media.paused) {
DCFill(state_dc);
GrBlot(state_dc, -9, 0, IMG_PLAYPAUS);
GrBlot(dc, 28, 28, state_dc);
}
if (player.media.stopped) {
DCFill(state_dc);
GrBlot(state_dc, -18, 0, IMG_PLAYPAUS);
GrBlot(dc, 28, 28, state_dc);
}
DCDel(state_dc);
}
U0 DrawPlayer(CTask *, CDC *dc) {
if (!player.media.loading) {
GrBlot(dc, 3, 2, IMG_MAIN);
UpdatePlayerMarqueeTextPos;
DrawPlayerMarqueeText(dc, 113, 29);
DrawPlayerBitRateSampleRate(dc, 114, 45);
DrawPlayerMonoStereo(dc, 212, 43);
DrawPlayerPlayPauseStopped(dc);
DrawPlayerTimeElapsed(dc);
DrawPlayerControlsActive(dc, 22, 91);
DrawPlayerPositionBar(dc, 21, 74);
}
}
U0 InitPlayer() {
MemSet(&player, sizeof(@player), NULL);
player.windowTask = Fs;
player.windowTask->win_width = 35;
WinHorz((TEXT_COLS / 2) - (player.windowTask->win_width / 2),
(TEXT_COLS / 2) - (player.windowTask->win_width / 2) +
(player.windowTask->win_width - 1),
player.windowTask);
player.windowTask->win_height = 15;
WinVert((TEXT_ROWS / 2) - (player.windowTask->win_height / 2),
(TEXT_ROWS / 2) - (player.windowTask->win_height / 2) +
(player.windowTask->win_height - 1),
player.windowTask);
player.images = CAlloc(sizeof(CDC *) * 32);
IMG_MAIN = PNGRead("T:/Images/MAIN.png");
IMG_CBUTTONS = PNGRead("T:/Images/CBUTTONS.png");
IMG_TEXT = PNGRead("T:/Images/TEXT.png");
IMG_NUMBERS = PNGRead("T:/Images/NUMBERS.png");
IMG_PLAYPAUS = PNGRead("T:/Images/PLAYPAUS.png");
IMG_MONOSTER = PNGRead("T:/Images/MONOSTER.png");
IMG_POSBAR = PNGRead("T:/Images/POSBAR.png");
DrawPlayerControlsIdle(IMG_MAIN, 19, 90);
// the control buttons are 23x18
SetPlayerMarqueeText(
"Welcome to TosAMP - MP3 Music Player for TempleOS *** ");
player.media.stopped = TRUE;
player.decoder_addr =
get_symbol_address("drmp3_open_memory_and_read_pcm_frames_s16");
player.audio_stream_id = @audio_get_available_output_stream;
player.windowTask->draw_it = &DrawPlayer;
Spawn(&PlayerTask, , "TOSamp Player Task", 1);
DocClear;
U8 *res = NULL;
while (1) {
StrCpy(player.windowTask->task_title, "...TOSamp...");
switch (Fs->user_data) {
case ACTION_PICK_FILE:
res = PopUpPickFile("T:/Music/");
Fs->user_data = NULL;
if (res)
if (StrCmp(res, ""))
OpenAndPlayFile(res);
break;
default:
break;
};
Sleep(1);
}
}