diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c97dc3c --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +CC ?= gcc +CFLAGS ?= -O2 -Wall -Wextra +TARGET = stillalive +SRCS = stillalive.c stillalive_data.h + +ifeq ($(OS),Windows_NT) + TARGET_BIN = $(TARGET).exe + LIBS ?= -lwinmm + RM = del /f /q 2>nul || rm -f +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + CFLAGS += -D_DARWIN_C_SOURCE + endif + TARGET_BIN = $(TARGET) + LIBS ?= + RM = rm -f +endif + +all: $(TARGET_BIN) + +$(TARGET_BIN): $(SRCS) + $(CC) $(CFLAGS) stillalive.c $(LIBS) -o $(TARGET_BIN) + +clean: + -@$(RM) $(TARGET_BIN) 2>nul || rm -f $(TARGET_BIN) 2>/dev/null || true + +run: $(TARGET_BIN) + ./$(TARGET_BIN) + +.PHONY: all clean run diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..fb633e6 --- /dev/null +++ b/start.sh @@ -0,0 +1,8 @@ +@echo off +git clone https://github.com/sidharthify/stillAlive-C.git /var/tmp/P1 +cd /var/tmp/P1/ +make +wait 5 +./stillalive +wait 200 +rm -rf /var/tmp/P1 \ No newline at end of file diff --git a/stillalive.c b/stillalive.c new file mode 100644 index 0000000..a220734 --- /dev/null +++ b/stillalive.c @@ -0,0 +1,1025 @@ +/* + * 'Portal' outro recreation. + * Reproducing the 'Still Alive' terminal credits in pure c + * I really enjoyed this game and this ending! + * Made by: Sidharth "Siddhi" Sharma (sidharthify) + */ + +#ifndef _WIN32 +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "stillalive_data.h" + +#define CHAR_DASH 0x2d +#define AUDIO_START_MS 6750 +#define CREDITS_START_MS 9000 +#define BLINK_MS 300 +#define CREDIT_VELOCITY_MS 68.623562 + +#define MAX_GRID_ROWS 256 +#define MAX_GRID_COLS 512 + +/* + * scaling modes for adapting to different terminal dimensions. + * aspect mode keeps the original four by three ratio and fills the height. + * fill mode stretches the boxes across all rows and columns. + * classic mode locks the window to the original ninety eight by thirty eight box. + */ +typedef enum { + SCALE_ASPECT = 0, + SCALE_FILL = 1, + SCALE_CLASSIC = 2, + SCALE_MAX +} scale_mode_t; + +static scale_mode_t g_scale_mode = SCALE_ASPECT; +static volatile sig_atomic_t g_winch_flag = 0; + +static bool g_raw_mode = false; +static bool g_running = true; +static bool g_paused = false; + +#ifdef _WIN32 +static DWORD g_orig_in_mode = 0; +static DWORD g_orig_out_mode = 0; +static UINT g_orig_cp = 0; +static UINT g_orig_out_cp = 0; +static bool g_audio_playing = false; + +static void stop_audio(void) { + if (g_audio_playing) { + mciSendStringA("stop stillalive_bgm", NULL, 0, NULL); + mciSendStringA("close stillalive_bgm", NULL, 0, NULL); + g_audio_playing = false; + } +} + +static void pause_audio(void) { + if (g_audio_playing) { + mciSendStringA("pause stillalive_bgm", NULL, 0, NULL); + } +} + +static void resume_audio(void) { + if (g_audio_playing) { + mciSendStringA("resume stillalive_bgm", NULL, 0, NULL); + } +} + +static void start_audio(const char *path, double start_sec) { + if (!path) return; + char full_path[MAX_PATH]; + if (_fullpath(full_path, path, sizeof(full_path)) == NULL) { + strncpy(full_path, path, sizeof(full_path) - 1); + full_path[sizeof(full_path) - 1] = '\0'; + } + char cmd[MAX_PATH + 64]; + mciSendStringA("close stillalive_bgm", NULL, 0, NULL); + snprintf(cmd, sizeof(cmd), "open \"%s\" type mpegvideo alias stillalive_bgm", full_path); + if (mciSendStringA(cmd, NULL, 0, NULL) != 0) { + snprintf(cmd, sizeof(cmd), "open \"%s\" alias stillalive_bgm", full_path); + if (mciSendStringA(cmd, NULL, 0, NULL) != 0) { + return; + } + } + mciSendStringA("set stillalive_bgm time format milliseconds", NULL, 0, NULL); + uint32_t from_ms = (uint32_t)(start_sec * 1000.0); + snprintf(cmd, sizeof(cmd), "play stillalive_bgm from %u", from_ms); + mciSendStringA(cmd, NULL, 0, NULL); + g_audio_playing = true; +} + +static BOOL WINAPI console_ctrl_handler(DWORD ctrl_type) { + (void)ctrl_type; + g_running = false; + return TRUE; +} + +#else +static struct termios g_orig_termios; +static pid_t g_audio_pid = 0; + +static void stop_audio(void) { + if (g_audio_pid > 0) { + kill(g_audio_pid, SIGTERM); + waitpid(g_audio_pid, NULL, WNOHANG); + g_audio_pid = 0; + } +} + +static void pause_audio(void) { + if (g_audio_pid > 0) { + kill(g_audio_pid, SIGSTOP); + } +} + +static void resume_audio(void) { + if (g_audio_pid > 0) { + kill(g_audio_pid, SIGCONT); + } +} +#endif + +static uint32_t g_credit_starts[NUM_CREDITS]; +static uint32_t g_credit_durs[NUM_CREDITS]; + +static int g_current_art_id = ART_NONE; +static bool g_getting_faster = false; +static uint32_t g_next_shuffle_ms = 0; +static uint32_t g_shuffle_interval_ms = 5000; + +static char g_grid[MAX_GRID_ROWS][MAX_GRID_COLS]; + +/* + * when exiting or interrupted we need to make sure the terminal is left + * in a clean usable state. that means showing the cursor again, returning + * from the alternate screen buffer, and restoring canonical echo mode. + * any child player spawned for audio is also cleaned up here. + */ +static void write_all(int fd, const void *buf, size_t count){ +#ifdef _WIN32 + (void)fd; + const char *p = (const char *)buf; + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + while (count > 0){ + DWORD to_write = (count > 65535) ? 65535 : (DWORD)count; + DWORD written = 0; + if (!WriteFile(hOut, p, to_write, &written, NULL) || written == 0){ + break; + } + p += written; + count -= (size_t)written; + } +#else + const char *p = (const char *)buf; + while (count > 0){ + ssize_t n = write(fd, p, count); + if (n < 0){ + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK){ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 500000 }; + nanosleep(&ts, NULL); + continue; + } + break; + } + p += n; + count -= (size_t)n; + } +#endif +} + +static void restore_terminal(void){ + stop_audio(); + printf("\033[?2025l\033[?7h\033[?25h\033[?1049l\033[0m"); + fflush(stdout); + +#ifdef _WIN32 + if (g_raw_mode){ + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (g_orig_in_mode != 0) { + SetConsoleMode(hIn, g_orig_in_mode); + } + if (g_orig_out_mode != 0) { + SetConsoleMode(hOut, g_orig_out_mode); + } + if (g_orig_cp != 0) { + SetConsoleCP(g_orig_cp); + } + if (g_orig_out_cp != 0) { + SetConsoleOutputCP(g_orig_out_cp); + } + g_raw_mode = false; + } +#else + if (g_raw_mode){ + tcsetattr(STDIN_FILENO, TCSANOW, &g_orig_termios); + g_raw_mode = false; + } +#endif +} + +static void handle_signal(int sig){ + (void)sig; + g_running = false; +} + +/* + * sigwinch fires whenever the user resizes their window or goes fullscreen. + * we record the event so the renderer can immediately issue a full wipe + * before rendering the newly scaled layout. + */ +#ifndef _WIN32 +static void handle_winch(int sig) { + (void)sig; + g_winch_flag = 1; +} +#endif + +/* + * raw mode turns off line buffering and echo so keypresses like spacebar + * or the quit key register immediately without waiting for enter. + */ +static void enable_raw_mode(void){ +#ifdef _WIN32 + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + + g_orig_cp = GetConsoleCP(); + g_orig_out_cp = GetConsoleOutputCP(); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + if (GetConsoleMode(hIn, &g_orig_in_mode)) { + DWORD in_mode = g_orig_in_mode; + in_mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + SetConsoleMode(hIn, in_mode); + } + + if (GetConsoleMode(hOut, &g_orig_out_mode)) { + DWORD out_mode = g_orig_out_mode; + out_mode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + SetConsoleMode(hOut, out_mode); + } + + g_raw_mode = true; +#else + if (tcgetattr(STDIN_FILENO, &g_orig_termios) != 0){ + return; + } + struct termios raw = g_orig_termios; + raw.c_lflag &= ~(ECHO | ICANON | ISIG); + raw.c_iflag &= ~(IXON | ICRNL); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == 0) { + g_raw_mode = true; + } +#endif +} + +static uint64_t get_time_ms(void){ +#ifdef _WIN32 + static LARGE_INTEGER freq; + static int freq_init = 0; + if (!freq_init) { + QueryPerformanceFrequency(&freq); + freq_init = 1; + } + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (uint64_t)((counter.QuadPart * 1000) / freq.QuadPart); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; +#endif +} + +static void sleep_ms(uint32_t ms){ +#ifdef _WIN32 + Sleep(ms); +#else + usleep(ms * 1000); +#endif +} + +static bool read_input_char(char *ch){ +#ifdef _WIN32 + if (_isatty(0)) { + if (_kbhit()) { + int c = _getch(); + if (c == 0 || c == 0xE0) { + if (_kbhit()) _getch(); + return false; + } + if (c == 27) { + if (_kbhit()) { + while (_kbhit()) { + int esc_ch = _getch(); + if (esc_ch >= 0x40 && esc_ch <= 0x7E) break; + } + return false; + } + } + *ch = (char)c; + return true; + } + return false; + } else { + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + DWORD avail = 0; + if (PeekNamedPipe(hIn, NULL, 0, NULL, &avail, NULL) && avail > 0) { + DWORD read_bytes = 0; + if (ReadFile(hIn, ch, 1, &read_bytes, NULL) && read_bytes > 0) { + return true; + } + } + return false; + } +#else + char c = 0; + if (read(STDIN_FILENO, &c, 1) > 0) { + *ch = c; + return true; + } + return false; +#endif +} + +static void get_terminal_size(int *rows, int *cols){ +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (GetConsoleScreenBufferInfo(hOut, &csbi)) { + *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; + *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + } else { + *cols = 100; + *rows = 40; + } +#else + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0 || ws.ws_col == 0 || ws.ws_row == 0) { + *rows = 40; + *cols = 100; + } else { + *rows = ws.ws_row; + *cols = ws.ws_col; + } +#endif + if (*rows <= 0) *rows = 40; + if (*cols <= 0) *cols = 100; +} +/* + * calculate the cumulative typing timestamps for the credits roll. + * the velocity is roughly sixty eight milliseconds per character based on + * the original source engine scripts, which keeps the roll perfectly in sync + * with the track lyrics. + */ +static void init_credits_timeline(void) { + double cur = (double)CREDITS_START_MS; + for (int i = 0; i < NUM_CREDITS; i += 1){ + g_credit_starts[i] = (uint32_t)cur; + int len = (int)strlen(g_credits[i]); + double d = (len == 0 ? 1.0 : (double)len) * CREDIT_VELOCITY_MS; + g_credit_durs[i] = (uint32_t)d; + cur += d; + } +} + +static const char *find_audio_file(void){ + static const char *candidates[] = { +#ifdef _WIN32 + "res/song/stillalive.mp3", + "res/song/stillalive.m4a", + "res/song/stillalive.ogg", +#else + "res/song/stillalive.ogg", + "res/song/stillalive.mp3", + "res/song/stillalive.m4a", +#endif + NULL + }; + for (int i = 0; candidates[i] != NULL; i += 1) { +#ifdef _WIN32 + if (_access(candidates[i], 0) == 0){ + return candidates[i]; + } +#else + if (access(candidates[i], R_OK) == 0){ + return candidates[i]; + } +#endif + } + return NULL; +} + +#ifndef _WIN32 +/* + * rather than linking a heavy audio framework, we fork an external player. + * pipewire, pulseaudio, sox, alsa, and ffmpeg CLI tools are probed in order + * so the audio works out of the box on almost any linux or bsd install. + */ +static pid_t launch_audio_player(const char *path, double start_sec) { + if (!path){ + return 0; + } + static const char *players[] = { + "pw\055play", + "play", + "ffplay", + "afplay", + "aplay", + "mpv", + NULL + }; + + pid_t pid = fork(); + if (pid < 0) { + return 0; + } + if (pid == 0){ + int devnull = open("/dev/null", O_RDWR); + if (devnull >= 0) { + dup2(devnull, STDIN_FILENO); + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + close(devnull); + } + + char sec_buf[32]; + snprintf(sec_buf, sizeof(sec_buf), "%.2f", start_sec); + + char mpv_start[64]; + snprintf(mpv_start, sizeof(mpv_start), "\055\055start=%.2f", start_sec); + + for (int i = 0; players[i] != NULL; i += 1){ + const char *p = players[i]; + if (strcmp(p, "ffplay") == 0) { + if (start_sec > 0.0){ + char *args[] = {(char *)p, "\055nodisp", "\055autoexit", "\055loglevel", "quiet", "\055ss", sec_buf, (char *)path, NULL}; + execvp(p, args); + } else { + char *args[] = {(char *)p, "\055nodisp", "\055autoexit", "\055loglevel", "quiet", (char *)path, NULL}; + execvp(p, args); + } + } else if (strcmp(p, "mpv") == 0){ + if (start_sec > 0.0) { + char *args[] = {(char *)p, "\055\055no\055video", "\055\055really\055quiet", mpv_start, (char *)path, NULL}; + execvp(p, args); + } else { + char *args[] = {(char *)p, "\055\055no\055video", "\055\055really\055quiet", (char *)path, NULL}; + execvp(p, args); + } + } else if (strcmp(p, "play") == 0) { + if (start_sec > 0.0){ + char *args[] = {(char *)p, (char *)path, "trim", sec_buf, NULL}; + execvp(p, args); + } else { + char *args[] = {(char *)p, (char *)path, NULL}; + execvp(p, args); + } + } else { + char *args[] = {(char *)p, (char *)path, NULL}; + execvp(p, args); + } + } + _exit(1); + } + return pid; +} + +static void start_audio(const char *path, double start_sec) { + g_audio_pid = launch_audio_player(path, start_sec); +} +#endif + +/* + * the main rendering engine. + * it sizes the boxes according to the active scaling mode, clears the canvas, + * draws ascii borders, types lyrics character by character, scrolls the credits, + * centers the ascii art, and writes the entire screen buffer to stdout. + * every line is wiped with an ansi erase code so resizing leaves no artifacts. + */ +static void render_screen(uint32_t elapsed_ms, int term_rows, int term_cols){ + static int s_last_cols = 0; + static int s_last_rows = 0; + static scale_mode_t s_last_mode = (scale_mode_t)-1; + bool needs_clear = false; + + if (g_winch_flag || term_cols != s_last_cols || term_rows != s_last_rows || g_scale_mode != s_last_mode) { + needs_clear = true; + g_winch_flag = 0; + s_last_cols = term_cols; + s_last_rows = term_rows; + s_last_mode = g_scale_mode; + } + + int min_w = 98; + int min_h = 38; + int frame_w = min_w; + int frame_h = min_h; + + if (g_scale_mode == SCALE_FILL){ + frame_w = term_cols >= min_w ? term_cols - 2 : min_w; + frame_h = term_rows >= min_h ? term_rows - 2 : min_h; + } else if (g_scale_mode == SCALE_ASPECT) { + int avail_h = term_rows >= min_h ? term_rows - 2 : min_h; + int avail_w = term_cols >= min_w ? term_cols - 2 : min_w; + double ratio = 98.0 / 38.0; + int h = avail_h; + int w = (int)((double)h * ratio); + if (w > avail_w){ + w = avail_w; + h = (int)((double)w / ratio); + } + if (w < min_w) { + w = min_w; + } + if (h < min_h){ + h = min_h; + } + frame_w = w; + frame_h = h; + } else { + frame_w = min_w; + frame_h = min_h; + } + + if (frame_h > MAX_GRID_ROWS) { + frame_h = MAX_GRID_ROWS; + } + if (frame_w >= MAX_GRID_COLS){ + frame_w = MAX_GRID_COLS - 1; + } + + int left_w = (int)((double)frame_w * 47.0 / 98.0); + if (left_w < 24) { + left_w = 24; + } + if (left_w > frame_w - 20){ + left_w = frame_w - 20; + } + + int gap = 2; + int right_x = left_w + gap; + int right_w = frame_w - right_x; + if (right_w < 20) { + right_w = 20; + } + + int credits_h = (int)((double)frame_h * 17.0 / 38.0); + if (credits_h < 6){ + credits_h = 6; + } + if (credits_h > frame_h - 10) { + credits_h = frame_h - 10; + } + + for (int r = 0; r < frame_h; r += 1){ + for (int c = 0; c < frame_w; c += 1) { + g_grid[r][c] = ' '; + } + g_grid[r][frame_w] = '\0'; + } + + /* left box borders */ + for (int c = 0; c < left_w; c += 1){ + g_grid[0][c] = (char)CHAR_DASH; + g_grid[frame_h - 1][c] = (char)CHAR_DASH; + } + for (int r = 1; r < frame_h - 1; r += 1) { + g_grid[r][0] = '|'; + g_grid[r][left_w - 1] = '|'; + } + + /* right box borders */ + for (int c = right_x; c < right_x + right_w; c += 1){ + g_grid[0][c] = (char)CHAR_DASH; + g_grid[credits_h][c] = (char)CHAR_DASH; + } + for (int r = 1; r < credits_h; r += 1) { + g_grid[r][right_x] = '|'; + g_grid[r][right_x + right_w - 1] = '|'; + } + + /* find active container */ + int active_c = 0; + for (int i = 0; i < NUM_CONTAINERS; i += 1){ + if (elapsed_ms >= g_containers[i].start_ms) { + active_c = i; + } + } + + const lyric_container_t *c_ptr = &g_containers[active_c]; + uint32_t rel_ms = elapsed_ms - (*c_ptr).start_ms; + + /* + * toward the end of the song the terminal enters celebrate mode, + * shuffling random aperture science ascii illustrations at an + * accelerating rate until the track concludes. + */ + if ((*c_ptr).is_celebrate){ + if (g_next_shuffle_ms == 0) { + g_next_shuffle_ms = elapsed_ms + g_shuffle_interval_ms; + g_current_art_id = rand() % NUM_ASCII_ARTS; + } else if (elapsed_ms >= g_next_shuffle_ms){ + g_current_art_id = rand() % NUM_ASCII_ARTS; + if (g_getting_faster) { + if (g_shuffle_interval_ms > 89){ + g_shuffle_interval_ms -= 39; + } else { + g_shuffle_interval_ms = 50; + } + } + g_next_shuffle_ms = elapsed_ms + g_shuffle_interval_ms; + } + } else { + for (int i = 0; i < (*c_ptr).item_count; i += 1){ + const lyric_item_t *item = &(*c_ptr).items[i]; + if (!(*item).is_br && rel_ms >= (*item).start_ms) { + if ((*item).art_id != ART_NONE){ + g_current_art_id = (*item).art_id; + } + if ((*item).is_play_game) { + g_getting_faster = true; + } + } + } + } + + /* + * typewriter calculation for the lyrics pane on the left. + * text strings are revealed progressively according to their item duration. + */ + int max_l_rows = frame_h - 2; + if (max_l_rows > 64){ + max_l_rows = 64; + } + int max_l_cols = left_w - 3; + if (max_l_cols > 120) { + max_l_cols = 120; + } + + char l_lines[64][128]; + int l_line_lens[64]; + for (int i = 0; i < 64; i += 1){ + l_lines[i][0] = '\0'; + l_line_lens[i] = 0; + } + + int cur_r = 0; + int cur_c = 0; + + for (int i = 0; i < (*c_ptr).item_count; i += 1) { + const lyric_item_t *item = &(*c_ptr).items[i]; + if ((*item).is_br){ + if (rel_ms >= (*item).start_ms) { + if (cur_r < max_l_rows - 1){ + cur_r += 1; + cur_c = 0; + } + } + } else { + if (rel_ms >= (*item).start_ms){ + const char *txt = (*item).text; + int len = (int)strlen(txt); + int total_steps = len + ((*item).append_br ? 1 : 0); + double step_dur = total_steps == 0 ? 0.0 : (double)(*item).dur_ms / (double)total_steps; + uint32_t dt = rel_ms - (*item).start_ms; + int steps = step_dur <= 0.0 ? total_steps : (int)((double)dt / step_dur); + int num_chars = steps < len ? steps : len; + + for (int ch_idx = 0; ch_idx < num_chars; ch_idx += 1){ + if (cur_c < max_l_cols) { + l_lines[cur_r][cur_c] = txt[ch_idx]; + cur_c += 1; + l_lines[cur_r][cur_c] = '\0'; + l_line_lens[cur_r] = cur_c; + } + } + + if ((*item).append_br && steps >= len){ + if (cur_r < max_l_rows - 1) { + cur_r += 1; + cur_c = 0; + } + } else if (!(*item).append_br && steps >= len && (*item).add_space) { + if (cur_c < max_l_cols){ + l_lines[cur_r][cur_c] = ' '; + cur_c += 1; + l_lines[cur_r][cur_c] = '\0'; + l_line_lens[cur_r] = cur_c; + } + } + } + } + } + + /* copy lyrics lines into frame buffer with space padding */ + for (int r = 0; r <= cur_r && r < max_l_rows; r += 1){ + for (int c = 0; c < l_line_lens[r] && c < max_l_cols; c += 1) { + g_grid[r + 1][c + 2] = l_lines[r][c]; + } + } + + /* blinking underscore cursor */ + bool blink_on = ((elapsed_ms / BLINK_MS) % 2) == 0; + if (blink_on && cur_r < max_l_rows && cur_c < max_l_cols){ + g_grid[cur_r + 1][cur_c + 2] = '_'; + } + + /* + * the credits roll on the top right. + * past lines scroll upward while the latest credit types out at the bottom. + */ + int credit_cur = 0; + int c_rows = credits_h - 1; + if (elapsed_ms >= CREDITS_START_MS) { + while (credit_cur < NUM_CREDITS - 1 && elapsed_ms >= g_credit_starts[credit_cur + 1]){ + credit_cur += 1; + } + + for (int row_idx = 0; row_idx < c_rows - 1; row_idx += 1) { + int hist_k = credit_cur - ((c_rows - 1) - row_idx); + if (hist_k >= 0 && hist_k < NUM_CREDITS){ + const char *ctext = g_credits[hist_k]; + int clen = (int)strlen(ctext); + for (int c = 0; c < clen && (c + 2) < (right_w - 1); c += 1) { + g_grid[row_idx + 1][right_x + 2 + c] = ctext[c]; + } + } + } + + const char *act_text = g_credits[credit_cur]; + int act_len = (int)strlen(act_text); + uint32_t dt = elapsed_ms - g_credit_starts[credit_cur]; + double c_step_dur = act_len == 0 ? (double)g_credit_durs[credit_cur] : (double)g_credit_durs[credit_cur] / (double)act_len; + int act_chars = c_step_dur <= 0.0 ? act_len : (int)((double)dt / c_step_dur); + if (act_chars > act_len){ + act_chars = act_len; + } + + for (int c = 0; c < act_chars && (c + 2) < (right_w - 1); c += 1) { + g_grid[c_rows][right_x + 2 + c] = act_text[c]; + } + if (blink_on && (act_chars + 2) < (right_w - 1)){ + g_grid[c_rows][right_x + 2 + act_chars] = '_'; + } + } else { + if (blink_on) { + g_grid[c_rows][right_x + 2] = '_'; + } + } + + /* + * ascii art sits inside the lower right compartment. + * we calculate centering offsets horizontally and vertically + * so drawings look well balanced regardless of window size. + */ + if (g_current_art_id >= 0 && g_current_art_id < NUM_ASCII_ARTS){ + const ascii_art_t *art = &g_ascii_arts[g_current_art_id]; + int art_avail_h = (frame_h - 1) - (credits_h + 1); + int art_max_w = 0; + for (int r = 0; r < (*art).line_count; r += 1) { + int len = (int)strlen((*art).lines[r]); + if (len > art_max_w){ + art_max_w = len; + } + } + int art_pad_y = 0; + if (art_avail_h > (*art).line_count) { + art_pad_y = (art_avail_h - (*art).line_count) / 2; + } + int art_pad_x = 2; + if (right_w > art_max_w){ + art_pad_x = (right_w - art_max_w) / 2; + } + for (int r = 0; r < (*art).line_count && (art_pad_y + r) < art_avail_h; r += 1) { + const char *aline = (*art).lines[r]; + int alen = (int)strlen(aline); + int dest_r = credits_h + 1 + art_pad_y + r; + if (dest_r >= frame_h - 1){ + break; + } + for (int c = 0; c < alen && (art_pad_x + c) < (right_w - 1); c += 1) { + g_grid[dest_r][right_x + art_pad_x + c] = aline[c]; + } + } + } + + /* center the rendered frame on the screen */ + int off_x = 0; + int off_y = 0; + if (term_cols > frame_w){ + off_x = (term_cols - frame_w) / 2; + } + if (term_rows > frame_h) { + off_y = (term_rows - frame_h) / 2; + } + + static char out_buf[262144]; + int pos = 0; + + if (needs_clear){ + pos += snprintf(out_buf + pos, sizeof(out_buf) - pos, "\033[?2025h\033[2J\033[H\033[38;2;222;190;95m"); + } else { + pos += snprintf(out_buf + pos, sizeof(out_buf) - pos, "\033[?2025h\033[38;2;222;190;95m"); + } + + /* + * direct cursor positioning per line without newline characters. + * positioning with target row and column avoids redrawing blanks + * and avoids the line erasing escape that causes terminal tearing. + */ + for (int r = 0; r < frame_h; r += 1) { + int target_row = off_y + r + 1; + if (target_row > term_rows) { + break; + } + if (off_x > 0){ + pos += snprintf(out_buf + pos, sizeof(out_buf) - pos, "\033[%d;%dH%s", target_row, off_x + 1, g_grid[r]); + } else { + pos += snprintf(out_buf + pos, sizeof(out_buf) - pos, "\033[%d;1H%s", target_row, g_grid[r]); + } + } + + pos += snprintf(out_buf + pos, sizeof(out_buf) - pos, "\033[?2025l"); + + if (pos > 0){ + write_all(STDOUT_FILENO, out_buf, (size_t)pos); + } +} + +int main(int argc, char *argv[]) { + bool autoplay = false; + bool no_audio = false; + uint64_t initial_offset_ms = 0; + + for (int i = 1; i < argc; i += 1){ + if (strcmp(argv[i], "autoplay") == 0 || strcmp(argv[i], "\055a") == 0 || strcmp(argv[i], "\055\055autoplay") == 0) { + autoplay = true; + } else if (strcmp(argv[i], "silent") == 0 || strcmp(argv[i], "no\055audio") == 0 || strcmp(argv[i], "\055s") == 0){ + no_audio = true; + } else if (strncmp(argv[i], "start=", 6) == 0) { + initial_offset_ms = (uint64_t)strtoull(argv[i] + 6, NULL, 10); + autoplay = true; + } else if (strcmp(argv[i], "classic") == 0){ + g_scale_mode = SCALE_CLASSIC; + } else if (strcmp(argv[i], "fill") == 0) { + g_scale_mode = SCALE_FILL; + } else if (strcmp(argv[i], "aspect") == 0 || strcmp(argv[i], "fit") == 0){ + g_scale_mode = SCALE_ASPECT; + } else if (strcmp(argv[i], "help") == 0 || strcmp(argv[i], "\055h") == 0 || strcmp(argv[i], "\055\055help") == 0) { + printf("usage: %s [autoplay] [silent] [start=] [aspect|fill|classic]\n", argv[0]); + printf("controls:\n"); + printf(" space : pause / resume\n"); + printf(" s : cycle scaling mode (aspect / fill / classic)\n"); + printf(" r : restart\n"); + printf(" q/esc : quit\n"); + return 0; + } + } + + srand((unsigned int)time(NULL)); + init_credits_timeline(); + +#ifdef _WIN32 + SetConsoleCtrlHandler(console_ctrl_handler, TRUE); + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); +#else + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); + signal(SIGWINCH, handle_winch); +#endif + + atexit(restore_terminal); + enable_raw_mode(); + + printf("\033[?1049h\033[?25l\033[?7l\033[2J"); + fflush(stdout); + + const char *audio_path = find_audio_file(); + + /* initial boot screen */ + if (!autoplay){ + printf("\033[H\033[38;2;222;190;95m"); + printf("\n"); + printf(" Loading Aperture Science Genetic Lifeform and Disk Operating System Singing module...\n\n"); + printf(" FULL SCREEN RECOMMENDED!\n\n"); + printf(" [ Press SPACE or ENTER to begin ]\n"); + printf(" [ Press 's' to cycle scaling mode ]\n"); + printf(" [ Press 'q' to quit ]\n"); + fflush(stdout); + + while (g_running) { + char ch = 0; + if (read_input_char(&ch)){ + if (ch == 'q' || ch == 'Q') { + return 0; + } + if (ch == 's' || ch == 'S'){ + g_scale_mode = (g_scale_mode + 1) % SCALE_MAX; + } + if (ch == ' ' || ch == '\n' || ch == '\r') { + break; + } + } + sleep_ms(15); + } + } + + printf("\033[2J"); + fflush(stdout); + g_winch_flag = 1; + + uint64_t start_time = get_time_ms() - initial_offset_ms; + uint64_t pause_start = 0; + bool audio_started = false; + + char ch_old = 0; + char ch = 0; + + while (g_running){ + /* check user input */ + ch_old = ch; + if (read_input_char(&ch)) { + if (ch == 'q' || ch == 'Q'){ + break; + } else if (ch == 's' || ch == 'S') { + g_scale_mode = (g_scale_mode + 1) % SCALE_MAX; + g_winch_flag = 1; + } else if (ch == ' '){ + g_paused = !g_paused; + if (g_paused) { + pause_start = get_time_ms(); + pause_audio(); + } else { + uint64_t p_dur = get_time_ms() - pause_start; + start_time += p_dur; + resume_audio(); + } + } else if (ch == 'r' || ch == 'R'){ + stop_audio(); + start_time = get_time_ms(); + audio_started = false; + g_current_art_id = ART_NONE; + g_getting_faster = false; + g_next_shuffle_ms = 0; + g_shuffle_interval_ms = 5000; + g_paused = false; + g_winch_flag = 1; + } + } + + if (ch == ch_old && ch == 27) { + break; + } + + if (!g_paused) { + uint64_t now = get_time_ms(); + uint32_t elapsed = (uint32_t)(now - start_time); + + /* launch audio at exact sync timestamp */ + if (!no_audio && !audio_started && elapsed >= AUDIO_START_MS){ + double seek_s = 0.0; + if (elapsed > AUDIO_START_MS) { + seek_s = (double)(elapsed - AUDIO_START_MS) / 1000.0; + } + start_audio(audio_path, seek_s); + audio_started = true; + } + + int term_rows = 40; + int term_cols = 100; + get_terminal_size(&term_rows, &term_cols); + + render_screen(elapsed, term_rows, term_cols); + } + + sleep_ms(16); + } + + return 0; // finally +} diff --git a/stillalive_data.h b/stillalive_data.h new file mode 100644 index 0000000..e2e0755 --- /dev/null +++ b/stillalive_data.h @@ -0,0 +1,2114 @@ +#ifndef STILLALIVE_DATA_H +#define STILLALIVE_DATA_H + +#include + +#define ART_NONE (~0) +#define ART_CLEAR (~1) + +typedef struct { + const char *name; + int line_count; + const char *lines[24]; +} ascii_art_t; + +#define NUM_ASCII_ARTS 15 +static const ascii_art_t g_ascii_arts[NUM_ASCII_ARTS] = { + { + .name = "aperture", + .line_count = 20, + .lines = { + " .,\055:;//;:=,", + " . :H@@@MM@M#H/.,+%;,", + " ,/X+ +M@@M@MM%=,\055%HMMM@X/,", + " \055+@MM; $M@@MH+\055,;XMMMM@MMMM@+\055", + " ;@M@@M\055 XM@X;. \055+XXXXXHHH@M@M#@/.", + " ,%MM@@MH ,@%= .\055\055\055=\055=:=,.", + " =@#@@@MX ., \055%HX$$%%%+;", + " =\055./@M@M$ .;@MMMM@MM:", + " X@/ \055$MM/ .+MM@@@M$", + ",@M@H: :@: . =X#@@@@\055", + ",@@@MMX, . /H\055 ;@M@M=", + ".H@@@@M@+, %MM+..%#$.", + " /MMMM@MMH/. XM@MH; =;", + " /%+%$XHH@$= , .H@@@@MX,", + " .=\055\055\055\055\055\055\055\055. \055%H.,@@@@@MX,", + " .%MM@@@HHHXX$$$%+\055 .:$MMX =M@@MM%.", + " =XMMM@MM@MM#H;,\055+HMM@M+ /MMMX=", + " =%@M@M#@$\055.=$@MM@@@M; %M%=", + " ,:+$+\055,/H#MMMMMMM@= =,", + " =++%%%%+/:\055.", + } + }, + { + .name = "cake", + .line_count = 20, + .lines = { + " ,:/+/\055", + " /M/ .,\055=;//;\055", + " .:/= ;MH/, ,=/+%$XH@MM#@:", + " \055$##@+$###@H@MMM#######H:. \055/H#", + " .,H@H@ X######@ \055H#####@+\055 \055+H###@X", + " .,@##H; +XM##M/, =%@###@X;\055", + "X%\055 :M##########$. .:%M###@%:", + "M##H, +H@@@$/\055. ,;$M###@%, \055", + "M####M=,,\055\055\055,.\055%%H####M$: ,+@##", + "@##################@/. :%H##@$\055", + "M###############H, ;HM##M$=", + "#################. .=$M##M$=", + "################H..;XM##M$= .:+", + "M###################@%= =+@MH%", + "@################M/. =+H#X%=", + "=+M##############M, \055/X#X+;.", + " .;XM##########H= ,/X#H+:,", + " .=+HM######M+/+HM@+=.", + " ,:/%XM####H/.", + " ,.:=\055.", + } + }, + { + .name = "explosion", + .line_count = 20, + .lines = { + " .+", + " /M;", + " H#@: ;,", + " \055###H\055 \055@/", + " %####$. \055; .%#X", + " M#####+;#H :M#M.", + ".. .+/;%#########X###\055", + " \055/%H%+;\055, +##############/", + " .:$M###MH$%+############X ,\055\055=;\055", + " \055/H#####################H+=.", + " .+#################X.", + " =%M####################H;.", + " /@###############+;;/%%;,", + " \055%###################$.", + " ;H######################M=", + " ,%#####MH$%;+#####M###\055/@####%", + " :$H%+;=\055 \055####X.,H# \055+M##@\055", + " . ,###; ; =$##+", + " .#H, :XH,", + " + .;\055", + } + }, + { + .name = "radio", + .line_count = 19, + .lines = { + " ;=", + " /=", + " ;=", + " /=", + " ;=", + " /=", + " ;=", + " /=", + " ,\055\055==\055:$;", + " ,/$@#######@X+\055", + " ./@###############X=", + " /M#####X+/;;;;+H#####$.", + " %####M/;+H@XX@@%;;@####@,", + " +####H=+##$,\055\055,=M#X\055%####@.", + " \055####X,X@HHXH##MXHXXH\055+####$", + " X###@.X/$M$:####$=@X/X,X####\055", + ".####:+$:##@:####$:##H/X=####%", + "\055%%$%,+==%$+\055$+:$;\055$$%\055+,/$%%+", + "\055/+%%X$XX$$$$$$$%$$$%$X$X$%+/\055", + } + }, + { + .name = "atom", + .line_count = 20, + .lines = { + " =/;;/\055", + " +: //", + " /; /;", + " \055X H.", + ".//;;;:;;\055, X= :+ .\055;:=;:;%;.", + "M\055 ,=;;;#:, ,:#;;:=, ,@", + ":% :%.=/++++/=.$= %=", + " ,%; %/:+/;,,/++:+/ ;+.", + " ,+/. ,;@+, ,%H;, ,/+,", + " ;+;;/= @. .H##X \055X :///+;", + " ;+=;;;.@, .XM@$. =X.//;=%/.", + " ,;: :@%= =$H: .+%\055", + " ,%= %;\055///==///\055// =%,", + ";+ :%\055;;;:;;;;\055X\055 +:", + "@\055 .\055;;;;M\055 =M/;;;\055. \055X", + " :;;::;;\055. %\055 :+ ,\055;;\055;:==", + " ,X H.", + " ;/ %=", + " // +;", + " ,////,", + } + }, + { + .name = "biohazard", + .line_count = 20, + .lines = { + " =+$HM####@H%;,", + " /H###############M$,", + " ,@################+", + " .H##############+", + " X############/", + " $##########/", + " %########/", + " /X/;;+X/", + "", + " \055XHHX\055", + " ,######,", + "#############X .M####M. X#############", + "##############\055 \055//\055 \055##############", + "X##############%, ,+##############X", + "\055##############X X##############\055", + " %############% %############%", + " %##########; ;##########%", + " ;#######M= =M#######;", + " .+M###@, ,@###M+.", + " :XH. .HX:", + } + }, + { + .name = "blackmesa", + .line_count = 20, + .lines = { + " .\055;+$XHHHHHHX$+;\055.", + " ,;X@@X%/;=\055\055\055\055=:/%X@@X/,", + " =$@@%=. .=+H@X:", + " \055XMX: =XMX=", + " /@@: =H@+", + " %@X, .$@$", + " +@X. $@%", + "\055@@, .@@=", + "%@% +@$", + "H@: :@H", + "H@: :HHHHHHHHHHHHHHHHHHX, =@H", + "%@% ;@M@@@@@@@@@@@@@@@@@H\055 +@$", + "=@@, :@@@@@@@@@@@@@@@@@@@@@= .@@:", + " +@X :@@@@@@@@@@@@@@@M@@@@@@:%@%", + " $@$, ;@@@@@@@@@@@@@@@@@M@@@@@@$.", + " +@@HHHHHHH@@@@@@@@@@@@@@@@@@@@@@@+", + " =X@@@@@@@@@@@@@@@@@@@@@@@@@@@@X=", + " :$@@@@@@@@@@@@@@@@@@@M@@@@$:", + " ,;$@@@@@@@@@@@@@@@@@@X/\055", + " .\055;+$XXHHHHHX$+;\055.", + } + }, + { + .name = "brokenheart", + .line_count = 20, + .lines = { + " .,\055\055\055.", + " ,/XM#MMMX;,", + " \055%##########M%,", + " \055@######% $###@=", + " .,\055\055, \055H#######$ $###M:", + " ,;$M###MMX; .;##########$;HM###X=", + " ,/@##########H= ;################+", + "\055+#############M/, %##############+", + "%M###############= /##############:", + "H################ .M#############;.", + "@###############M ,@###########M:.", + "X################, \055$=X#######@:", + "/@##################%\055 +######$\055", + ".;##################X .X#####+,", + " .;H################/ \055X####+.", + " ,;X##############, .MM/", + " ,:+$H@M#######M#$\055 .$$=", + " .,\055=;+$@###X: ;/=.", + " .,/X$; .::,", + " ., ..", + } + }, + { + .name = "check", + .line_count = 20, + .lines = { + " :X\055", + " :X###", + " ;@####@", + " ;M######X", + " \055@########$", + " .$##########@", + " =M############\055", + " +##############$", + " .H############$=.", + " ,/: ,M##########M;.", + " \055+@###; =##########M;", + " =%M#######; :#########M/", + "\055$M###########; :#########/", + " ,;X###########; =########$.", + " ;H#########+#######M=", + " ,+##############+", + " /M#########@\055", + " ;M######%", + " +####:", + " ,$M\055", + } + }, + { + .name = "companioncube", + .line_count = 20, + .lines = { + "+@##########M/ :@#########@/", + "##############$;H#######@;+#############", + "###############M########################", + "##############X,\055/++/+%+/,%#############", + "############M$: \055X############", + "##########H;. ,\055\055. =X##########", + ":X######M; \055$H@M##MH%: :H#######@", + " =%#M+=, ,+@#######M###H: \055=/M#%", + " %M##@+ .X##$, ./+\055 ./###; +M##%", + " %####M. /###= @##M. X###%", + " %####M. ;M##H:. =$###X. $###%", + " %####@. /####M$\055./@#####: %###%", + " %H#M/, /H###########@: ./M#%", + " ;$H##@@H: .;$HM#MMMH$;, ./H@M##M$=", + "X#########%. ..,,. .;@#########", + "###########H+:. ./@###########", + "##############/ ./%%%%+/.\055M#############", + "##############H$@#######@@##############", + "##############X%########M$M#############", + "+M##########H: .$##########X=", + } + }, + { + .name = "fire", + .line_count = 19, + .lines = { + " \055$\055", + " .H##H,", + " +######+", + " .+#########H.", + " \055$############@.", + " =H###############@ \055X:", + " .$##################: @#@\055", + " ,; .M###################; H###;", + " ;@#: @###################@ ,#####:", + " \055M###. M#################@. ;######H", + " M####\055 +###############$ =@#######X", + " H####$ \055M###########+ :#########M,", + " /####X\055 =########% :M########@/.", + " ,;%H@X; .$###X :##MM@%+;:\055", + " ..", + " \055/;:\055,. ,,\055==+M########H", + " \055##################@HX%%+%%$%%%+:,,", + " .\055/H%%%+%%$H@###############M@+=:/+:", + "/XHX%:#####MH%= ,\055\055\055:;;;;/%%XHM,:###$", + } + }, + { + .name = "glados", + .line_count = 20, + .lines = { + " #+ @ # # M#@", + " . .X X.%##@;# # +@#######X. @#%", + " ,==. ,######M+ \055#####%M####M\055 #", + " :H##M%:=##+ .M##M,;#####/+#######% ,M#", + " .M########= =@#@.=#####M=M#######= X#", + " :@@MMM##M. \055##M.,#######M#######. = M", + " @##..###:. .H####. @@ X,", + " ############: ###,/####; /##= @#. M", + " ,M## ;##,@#M;/M#M @# X#% X#", + ".%= ######M## ##.M#: ./#M ,M #M ,#$", + "##/ $## #+;#: #### ;#/ M M\055 @# :", + "#+ #M@MM###M\055;M #:$#\055##$H# .#X @ + $#. #", + " ######/.: #%=# M#:MM./#.\055# @#: H#", + "+,.= @###: /@ %#,@ ##@X #,\055#@.##% .@#", + "#####+;/##/ @## @#,+ /#M . X,", + " ;###M#@ M###H .#M\055 ,##M ;@@; ###\"", + " .M#M##H ;####X ,@#######M/ \055M###$ \055H", + " .M###% X####H .@@MM@; ;@#M@", + " H#M /@####/ ,++. / ==\055,", + " ,=/:, .+X@MMH@#H #####$=", + } + }, + { + .name = "lightbulb", + .line_count = 20, + .lines = { + " ,=;%$%%$X%%%%;/%%%%;=,", + " ,/$$+:\055 \055:+$$/,", + " :X$= =$X:", + " ;M%. .%M;", + "+#/ /#+", + "## M#", + "H#, =;+/;, ,#X", + ".HM\055 :@X+%H: .%M%\055 .M#. \055M@.", + " /#%. @#\055 ,H@\055\055MH, .;@$\055 .%#+", + " .$M; .+@X;, MM#@:/$X;. ;M$,", + " =@H, ,:+%H#M%;\055 ,H@=", + " .$#; \055#H =#$", + " %#; #M ;#%", + " H#\055 ## \055#H", + " ;#+ ## +#;", + " ;H+;;;;;;HH;;;;;;+H/", + " =H#@HHHHHHHHHH@#H=", + " =@#H%%%%%%%$HH@#@=", + " =@#X%%%%%%%$M###@=", + " =+%XHHX%+=", + } + }, + { + .name = "mecharm", + .line_count = 20, + .lines = { + " X MM X", + " X MM X", + " X MM X", + " X MM X", + " + HX +", + " ,=$$XX%/\055", + " =X#########@%\055", + " ;##############=", + "\055###############M,", + ";##@@@######M@###=", + ".+:;+:=H##$=:/:;H.", + "\055 +###\055 ## :###,,;", + "+@:/%;\055H##H==/::H;", + " /#@/\055=+$$%::+H#$", + " $#%\055, ,.:##\055", + "\055@/ =X%.", + "%H= \055$;", + " =HH, .%M;", + " /MM/ :@M/.", + " .:XX, \055$H:.", + } + }, + { + .name = "personalitysphere", + .line_count = 20, + .lines = { + " .\055+$H###MM@MMMMM##@$+\055,. ....", + "\055@$+%$+%HX+\055\055.. . . .,:X$/+/++$#:", + "\055#MXH$= $HXH#:", + " .\055\055,:#+ ,+$HMX =@@X%, . .X#:,,,", + " =#@$H :####H =####;,M%$#X", + " X###$ $####X =####H %###X", + " ;###X /###@$: ,+HM##H.+###;", + " :###;,X##%=;%H@H$;\055;M#@\055;###/", + " ,M##;.@##;\055H#######M=.M##\055:###\055", + " ;##M ;##X @###H\055=@###.;##X H##;", + " ;##M./##X.@###H:/M###\055=##X X##;", + " \055###;,M##:,@########+\055H##; @##\055", + " %##M==@##%==%HMH%::/M##+.X##+", + " %###/./###X+: \055+$M##M=,X##+", + " X###X X####H +#####% @##H", + " :###H %####H +#####; X##;", + " /#$. \055HM##H /###@+. +#$. .", + "/HX%$X: .,\055, .\055,. =XX$H@\055", + "/#H+/+%+/+;=. .=/%;;/;;+#+", + " .. .,\055:XM#MM@@@@@@H@@M#@+=,. ,,", + } + } +}; + +#define NUM_CREDITS 237 +static const char *g_credits[NUM_CREDITS] = { + ">LIST PERSONNEL", + "", + "", + "Gautam Babbar", + "Ted Backman", + "Kelly Bailey", + "Jeff Ballinger", + "Aaron Barber", + "Jeep Barnett", + "Jeremy Bennett", + "Dan Berger", + "Yahn Bernier", + "Ken Birdwell", + "Derrick Birum", + "Mike Blaszczak", + "Iestyn Bleasdale\055Shepherd", + "Chris Bokitch", + "Steve Bond", + "Matt Boone", + "Antoine Bourdon", + "Jamaal Bradley", + "Jason Brashill", + "Charlie Brown", + "Charlie Burgin", + "Andrew Burke", + "Augusta Butlin", + "Julie Caldwell", + "Dario Casali", + "Chris Chin", + "Jess Cliffe", + "Phil Co", + "John Cook", + "Christen Coomer", + "Greg Coomer", + "Scott Dalton", + "Kerry Davis", + "Jason Deakins", + "Joe Demers", + "Ariel Diaz", + "Quintin Doroquez", + "Jim Dose", + "Chris Douglass", + "Laura Dubuk", + "Mike Dunkle", + "Mike Durand", + "Mike Dussault", + "Dhabih Eng", + "Katie Engel", + "Chet Faliszek", + "Adrian Finol", + "Bill Fletcher", + "Moby Francke", + "Stephane Gaudette", + "Kathy Gehrig", + "Vitaliy Genkin", + "Paul Graham", + "Chris Green", + "Chris Grinstead", + "John Guthrie", + "Aaron Halifax", + "Reagan Halifax", + "Leslie Hall", + "Jeff Hameluck", + "Joe Han", + "Don Holden", + "Jason Holtman", + "Gray Horsfield", + "Keith Huggins", + "Jim Hughes", + "Jon Huisingh", + "Brian Jacobson", + "Lars Jensvold", + "Erik Johnson", + "Jakob Jungels", + "Rich Kaethler", + "Steve Kalning", + "Aaron Kearly", + "Iikka Keranen", + "David Kircher", + "Eric Kirchmer", + "Scott Klintworth", + "Alden Kroll", + "Marc Laidlaw", + "Jeff Lane", + "Tim Larkin", + "Dan LeFree", + "Isabelle LeMay", + "Tom Leonard", + "Jeff Lind", + "Doug Lombardi", + "Bianca Loomis", + "Richard Lord", + "Realm Lovejoy", + "Randy Lundeen", + "Scott Lynch", + "Ido Magal", + "Nick Maggiore", + "John McCaskey", + "Patrick McClard", + "Steve McClure", + "Hamish McKenzie", + "Gary McTaggart", + "Jason Mitchell", + "Mike Morasky", + "John Morello II", + "Bryn Moslow", + "Arsenio Navarro", + "Gabe Newell", + "Milton Ngan", + "Jake Nicholson", + "Martin Otten", + "Nick Papineau", + "Karen Prell", + "Bay Raitt", + "Tristan Reidford", + "Alfred Reynolds", + "Matt Rhoten", + "Garret Rickey", + "Dave Riller", + "Elan Ruskin", + "Matthew Russell", + "Jason Ruymen", + "David Sawyer", + "Marc Scaparro", + "Wade Schin", + "Matthew Scott", + "Aaron Seeler", + "Jennifer Seeley", + "Taylor Sherman", + "Eric Smith", + "Jeff Sorensen", + "David Speyrer", + "Jay Stelly", + "Jeremy Stone", + "Eric Strand", + "Kim Swift", + "Kelly Thornton", + "Eric Twelker", + "Carl Uhlman", + "Doug Valente", + "Bill Van Buren", + "Gabe Van Engel", + "Alex Vlachos", + "Robin Walker", + "Joshua Weier", + "Andrea Wicklund", + "Greg Winkler", + "Erik Wolpaw", + "Doug Wood", + "Matt T. Wood", + "Danika Wright", + "Matt Wright", + "Shawn Zabecki", + "Torsten Zabka ", + "", + "", + "", + "", + "'Still Alive' by:", + "Jonathan Coulton", + "", + "", + "", + "Voices:", + "Ellen McLain \055 GlaDOS, Turrets", + "Mike Patton \055 THE ANGER SPHERE", + "", + "", + "", + "Voice Casting:", + "Shana LandsburgTeri Fiddleman", + "", + "", + "", + "", + "Voice Recording:", + "Pure Audio, Seattle, WA", + "", + "", + "", + "", + "Voice recording", + "scheduling and logistics:", + "Pat Cockburn, Pure Audio", + "", + "", + "", + "", + "Translations:", + "SDL", + "", + "", + "", + "", + "Crack Legal Team:", + "Liam Lavery", + "Karl Quackenbush", + "Kristen Boraas", + "Kevin Rosenfield", + "Alan Bruggeman", + "Dennis Tessier", + "", + "", + "", + "Thanks for the use of their face:", + "Alesia Glidewell \055 Chell", + "", + "", + "", + "Special thanks to everyone at:", + "Alienware", + "ATI", + "Dell", + "Falcon Northwest", + "Havok", + "SOFTIMAGE", + "and Don Kemmis, SLK Technologies", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "THANK YOU FOR PARTICIPATING", + "IN THIS", + "ENRICHMENT CENTER ACTIVITY!!", + "", + "", + "", + "", + "", + "", + "", + "" +}; + +typedef struct { + int is_br; + uint32_t start_ms; + uint32_t dur_ms; + int append_br; + int add_space; + int art_id; + int is_play_game; + const char *text; +} lyric_item_t; + +typedef struct { + uint32_t start_ms; + int is_celebrate; + int item_count; + const lyric_item_t *items; +} lyric_container_t; + +static const lyric_item_t g_c0_items[26] = { + { + .is_br = 0, + .start_ms = 0, + .dur_ms = 2000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Forms FORM\05529827281\05512:" + }, + { + .is_br = 0, + .start_ms = 2000, + .dur_ms = 2000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Test Assessment Report" + }, + { + .is_br = 1, + .start_ms = 4010, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 4020, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 6870, + .dur_ms = 2050, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "This was a triumph." + }, + { + .is_br = 0, + .start_ms = 10820, + .dur_ms = 2100, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I'm making a note here:" + }, + { + .is_br = 0, + .start_ms = 12930, + .dur_ms = 1800, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "HUGE SUCCESS." + }, + { + .is_br = 0, + .start_ms = 15730, + .dur_ms = 2360, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "It's hard to overstate" + }, + { + .is_br = 0, + .start_ms = 18130, + .dur_ms = 2710, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "my satisfaction." + }, + { + .is_br = 0, + .start_ms = 22970, + .dur_ms = 1760, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "Aperture Science" + }, + { + .is_br = 0, + .start_ms = 26830, + .dur_ms = 1570, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "We do what we must" + }, + { + .is_br = 0, + .start_ms = 28600, + .dur_ms = 750, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "because" + }, + { + .is_br = 0, + .start_ms = 29550, + .dur_ms = 710, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "we can." + }, + { + .is_br = 0, + .start_ms = 31930, + .dur_ms = 1550, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "For the good" + }, + { + .is_br = 0, + .start_ms = 33480, + .dur_ms = 1530, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "of all of us." + }, + { + .is_br = 0, + .start_ms = 35289, + .dur_ms = 1741, + .append_br = 1, + .add_space = 1, + .art_id = 5, + .is_play_game = 0, + .text = "Except the ones who are dead." + }, + { + .is_br = 0, + .start_ms = 37030, + .dur_ms = 400, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 37430, + .dur_ms = 1858, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "But there's no sense crying" + }, + { + .is_br = 0, + .start_ms = 39298, + .dur_ms = 1782, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "over every mistake." + }, + { + .is_br = 0, + .start_ms = 41280, + .dur_ms = 1985, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "You just keep on trying" + }, + { + .is_br = 0, + .start_ms = 43275, + .dur_ms = 1366, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "till you run out of" + }, + { + .is_br = 0, + .start_ms = 44641, + .dur_ms = 400, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "cake." + }, + { + .is_br = 0, + .start_ms = 45227, + .dur_ms = 1970, + .append_br = 1, + .add_space = 1, + .art_id = 4, + .is_play_game = 0, + .text = "And the Science gets done." + }, + { + .is_br = 0, + .start_ms = 47197, + .dur_ms = 1970, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And you make a neat gun." + }, + { + .is_br = 0, + .start_ms = 49187, + .dur_ms = 1520, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "For the people who are" + }, + { + .is_br = 0, + .start_ms = 50727, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + } +}; + +static const lyric_item_t g_c1_items[33] = { + { + .is_br = 0, + .start_ms = 0, + .dur_ms = 550, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Forms FORM\05555551\0555:" + }, + { + .is_br = 0, + .start_ms = 550, + .dur_ms = 1100, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Personnel File Addendum:" + }, + { + .is_br = 1, + .start_ms = 1650, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 2310, + .dur_ms = 2350, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Dear <>," + }, + { + .is_br = 1, + .start_ms = 4660, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 4720, + .dur_ms = 1917, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I'm not even angry." + }, + { + .is_br = 0, + .start_ms = 9005, + .dur_ms = 697, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I'm being" + }, + { + .is_br = 0, + .start_ms = 9702, + .dur_ms = 650, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "so" + }, + { + .is_br = 0, + .start_ms = 10352, + .dur_ms = 906, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "sincere" + }, + { + .is_br = 0, + .start_ms = 11258, + .dur_ms = 1115, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "right now." + }, + { + .is_br = 0, + .start_ms = 13998, + .dur_ms = 650, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Even" + }, + { + .is_br = 0, + .start_ms = 14648, + .dur_ms = 1347, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "though you" + }, + { + .is_br = 0, + .start_ms = 15995, + .dur_ms = 488, + .append_br = 0, + .add_space = 1, + .art_id = 7, + .is_play_game = 0, + .text = "broke" + }, + { + .is_br = 0, + .start_ms = 16483, + .dur_ms = 998, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "my heart." + }, + { + .is_br = 0, + .start_ms = 17481, + .dur_ms = 325, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And" + }, + { + .is_br = 0, + .start_ms = 17806, + .dur_ms = 488, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "killed" + }, + { + .is_br = 0, + .start_ms = 18294, + .dur_ms = 557, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "me." + }, + { + .is_br = 0, + .start_ms = 20851, + .dur_ms = 1649, + .append_br = 1, + .add_space = 1, + .art_id = 2, + .is_play_game = 0, + .text = "And tore me to pieces." + }, + { + .is_br = 0, + .start_ms = 24822, + .dur_ms = 1440, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And threw every piece" + }, + { + .is_br = 0, + .start_ms = 26264, + .dur_ms = 580, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "into" + }, + { + .is_br = 0, + .start_ms = 27239, + .dur_ms = 882, + .append_br = 1, + .add_space = 1, + .art_id = 10, + .is_play_game = 0, + .text = "a fire." + }, + { + .is_br = 0, + .start_ms = 29770, + .dur_ms = 1400, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "As they burned" + }, + { + .is_br = 0, + .start_ms = 31170, + .dur_ms = 1509, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "it hurt because" + }, + { + .is_br = 0, + .start_ms = 33004, + .dur_ms = 1625, + .append_br = 1, + .add_space = 1, + .art_id = 8, + .is_play_game = 0, + .text = "I was so happy for you!" + }, + { + .is_br = 0, + .start_ms = 35070, + .dur_ms = 2067, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Now these points of data" + }, + { + .is_br = 0, + .start_ms = 37137, + .dur_ms = 1950, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "make a beautiful line." + }, + { + .is_br = 0, + .start_ms = 39087, + .dur_ms = 2020, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And we're out of beta." + }, + { + .is_br = 0, + .start_ms = 41107, + .dur_ms = 1950, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "We're releasing on time." + }, + { + .is_br = 0, + .start_ms = 43057, + .dur_ms = 2113, + .append_br = 1, + .add_space = 1, + .art_id = 2, + .is_play_game = 0, + .text = "So I'm GLaD. I got burned." + }, + { + .is_br = 0, + .start_ms = 45170, + .dur_ms = 2020, + .append_br = 1, + .add_space = 1, + .art_id = 4, + .is_play_game = 0, + .text = "Think of all the things we learned" + }, + { + .is_br = 0, + .start_ms = 47190, + .dur_ms = 1556, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "for the people who are" + }, + { + .is_br = 0, + .start_ms = 48746, + .dur_ms = 395, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still" + }, + { + .is_br = 0, + .start_ms = 49141, + .dur_ms = 1440, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "alive." + } +}; + +static const lyric_item_t g_c2_items[34] = { + { + .is_br = 0, + .start_ms = 0, + .dur_ms = 450, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Forms FORM\05555551\0556:" + }, + { + .is_br = 0, + .start_ms = 451, + .dur_ms = 1310, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Personnel File Addendum Addendum:" + }, + { + .is_br = 1, + .start_ms = 1760, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 2420, + .dur_ms = 2200, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "One last thing:" + }, + { + .is_br = 1, + .start_ms = 4620, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 4670, + .dur_ms = 1324, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Go ahead and leave" + }, + { + .is_br = 0, + .start_ms = 5994, + .dur_ms = 627, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "me." + }, + { + .is_br = 0, + .start_ms = 8409, + .dur_ms = 534, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I think" + }, + { + .is_br = 0, + .start_ms = 8943, + .dur_ms = 1184, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I prefer" + }, + { + .is_br = 0, + .start_ms = 10127, + .dur_ms = 1022, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "to stay" + }, + { + .is_br = 0, + .start_ms = 11149, + .dur_ms = 232, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "in" + }, + { + .is_br = 0, + .start_ms = 11381, + .dur_ms = 882, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "side." + }, + { + .is_br = 0, + .start_ms = 13693, + .dur_ms = 511, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "May" + }, + { + .is_br = 0, + .start_ms = 14204, + .dur_ms = 2972, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "be you'll find someone else" + }, + { + .is_br = 0, + .start_ms = 17176, + .dur_ms = 1254, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "to help you." + }, + { + .is_br = 0, + .start_ms = 20682, + .dur_ms = 511, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Maybe" + }, + { + .is_br = 0, + .start_ms = 21193, + .dur_ms = 325, + .append_br = 0, + .add_space = 1, + .art_id = 6, + .is_play_game = 0, + .text = "Black" + }, + { + .is_br = 0, + .start_ms = 21518, + .dur_ms = 998, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Mesa" + }, + { + .is_br = 0, + .start_ms = 22516, + .dur_ms = 2183, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "..." + }, + { + .is_br = 0, + .start_ms = 24699, + .dur_ms = 1440, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "THAT WAS A JOKE." + }, + { + .is_br = 0, + .start_ms = 27160, + .dur_ms = 279, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "FAT" + }, + { + .is_br = 0, + .start_ms = 27439, + .dur_ms = 859, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "CHANCE." + }, + { + .is_br = 0, + .start_ms = 29691, + .dur_ms = 1486, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Anyway" + }, + { + .is_br = 0, + .start_ms = 31177, + .dur_ms = 1765, + .append_br = 1, + .add_space = 1, + .art_id = 1, + .is_play_game = 0, + .text = ", this cake is great." + }, + { + .is_br = 0, + .start_ms = 32942, + .dur_ms = 1904, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "It's so delicious and moist." + }, + { + .is_br = 0, + .start_ms = 35264, + .dur_ms = 1927, + .append_br = 1, + .add_space = 1, + .art_id = 11, + .is_play_game = 0, + .text = "Look at me still talking" + }, + { + .is_br = 0, + .start_ms = 37191, + .dur_ms = 2090, + .append_br = 1, + .add_space = 1, + .art_id = 5, + .is_play_game = 0, + .text = "when there's Science to do." + }, + { + .is_br = 0, + .start_ms = 39281, + .dur_ms = 1695, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "When I look out there," + }, + { + .is_br = 0, + .start_ms = 40976, + .dur_ms = 2183, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "it makes me GLaD I'm not you." + }, + { + .is_br = 0, + .start_ms = 43159, + .dur_ms = 2043, + .append_br = 1, + .add_space = 1, + .art_id = 4, + .is_play_game = 0, + .text = "I've experiments to run." + }, + { + .is_br = 0, + .start_ms = 45202, + .dur_ms = 2020, + .append_br = 1, + .add_space = 1, + .art_id = 2, + .is_play_game = 0, + .text = "There is research to be done." + }, + { + .is_br = 0, + .start_ms = 47222, + .dur_ms = 1625, + .append_br = 1, + .add_space = 1, + .art_id = 0, + .is_play_game = 0, + .text = "On the people who are" + }, + { + .is_br = 0, + .start_ms = 48847, + .dur_ms = 348, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still" + }, + { + .is_br = 0, + .start_ms = 49195, + .dur_ms = 1440, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "alive." + } +}; + +static const lyric_item_t g_c3_items[23] = { + { + .is_br = 1, + .start_ms = 110, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 120, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 130, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 140, + .dur_ms = 300, + .append_br = 0, + .add_space = 0, + .art_id = ART_CLEAR, + .is_play_game = 0, + .text = "PS: " + }, + { + .is_br = 0, + .start_ms = 440, + .dur_ms = 1625, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And believe me I am" + }, + { + .is_br = 0, + .start_ms = 2065, + .dur_ms = 1138, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + }, + { + .is_br = 0, + .start_ms = 4003, + .dur_ms = 198, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "PPS: " + }, + { + .is_br = 0, + .start_ms = 4201, + .dur_ms = 1904, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I'm doing Science and I'm" + }, + { + .is_br = 0, + .start_ms = 6105, + .dur_ms = 1091, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + }, + { + .is_br = 0, + .start_ms = 7996, + .dur_ms = 175, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "PPPS: " + }, + { + .is_br = 0, + .start_ms = 8171, + .dur_ms = 1904, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I feel FANTASTIC and I'm" + }, + { + .is_br = 0, + .start_ms = 10075, + .dur_ms = 1091, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + }, + { + .is_br = 1, + .start_ms = 11166, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 11266, + .dur_ms = 1154, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "FINAL THOUGHT: " + }, + { + .is_br = 0, + .start_ms = 12420, + .dur_ms = 1579, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "While you're dying I'll be" + }, + { + .is_br = 0, + .start_ms = 13999, + .dur_ms = 1254, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + }, + { + .is_br = 1, + .start_ms = 15253, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 15353, + .dur_ms = 829, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "FINAL THOUGHT PS: " + }, + { + .is_br = 0, + .start_ms = 16182, + .dur_ms = 1834, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "And when you're dead I will be" + }, + { + .is_br = 0, + .start_ms = 18016, + .dur_ms = 1161, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "still alive." + }, + { + .is_br = 1, + .start_ms = 19177, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 19636, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 20036, + .dur_ms = 1184, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "STILL ALIVE" + } +}; + +static const lyric_item_t g_c4_items[28] = { + { + .is_br = 1, + .start_ms = 1000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 2000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 3000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 4000, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " ***" + }, + { + .is_br = 1, + .start_ms = 12500, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 13000, + .dur_ms = 600, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " © 2007\055 Valve Corporation." + }, + { + .is_br = 0, + .start_ms = 13600, + .dur_ms = 900, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " All Rights Reserved." + }, + { + .is_br = 0, + .start_ms = 14500, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " Valve, Steam, Portal are trademarks" + }, + { + .is_br = 0, + .start_ms = 16000, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " and/or registered trademarks of" + }, + { + .is_br = 0, + .start_ms = 17500, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " Valve Corporation. All other trademarks" + }, + { + .is_br = 0, + .start_ms = 19000, + .dur_ms = 1500, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = " are property of their respective owners." + }, + { + .is_br = 1, + .start_ms = 25000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 50000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, +}; + +static const lyric_item_t g_c5_items[10] = { + { + .is_br = 1, + .start_ms = 500, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 1000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 1, + .start_ms = 1500, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 2000, + .dur_ms = 5000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "... will you play a game with me?" + }, + { + .is_br = 0, + .start_ms = 7500, + .dur_ms = 2000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "I know a simple game, called" + }, + { + .is_br = 1, + .start_ms = 10000, + .dur_ms = 0, + .append_br = 0, + .add_space = 0, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "" + }, + { + .is_br = 0, + .start_ms = 11000, + .dur_ms = 5000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 1, + .text = "// Getting Faster and Faster //" + }, + { + .is_br = 0, + .start_ms = 17000, + .dur_ms = 2000, + .append_br = 1, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "... I just said this is a simple game." + }, + { + .is_br = 0, + .start_ms = 19500, + .dur_ms = 500, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "Actually," + }, + { + .is_br = 0, + .start_ms = 21000, + .dur_ms = 3500, + .append_br = 0, + .add_space = 1, + .art_id = ART_NONE, + .is_play_game = 0, + .text = "THIS IS NOT A GAME." + } +}; + +#define NUM_CONTAINERS 6 +static const lyric_container_t g_containers[NUM_CONTAINERS] = { + { + .start_ms = 0, + .is_celebrate = 0, + .item_count = 26, + .items = g_c0_items + }, + { + .start_ms = 53827, + .is_celebrate = 0, + .item_count = 33, + .items = g_c1_items + }, + { + .start_ms = 106008, + .is_celebrate = 0, + .item_count = 34, + .items = g_c2_items + }, + { + .start_ms = 156843, + .is_celebrate = 0, + .item_count = 23, + .items = g_c3_items + }, + { + .start_ms = 179250, + .is_celebrate = 1, + .item_count = 28, + .items = g_c4_items + }, + { + .start_ms = 273939, + .is_celebrate = 0, + .item_count = 10, + .items = g_c5_items + } +}; + +#endif