summaryrefslogtreecommitdiffhomepage
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c262
1 files changed, 170 insertions, 92 deletions
diff --git a/src/main.c b/src/main.c
index 9bd9ee0..d61c32c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -25,9 +25,9 @@ typedef struct {
int silenceCount;
bool studyMode;
bool wasInSilence; /* for detecting silence entry */
- bool pausedAtSilence; /* paused by study mode at silence */
+ int lastSilenceIdx; /* index of silence region we were last in, or -1 */
int skipAutoUpdate; /* frames to skip auto-updating currentTime */
- double lastVPress; /* timestamp of last V press for double-tap */
+ double lastVPress; /* unused, kept for struct compat */
} PlayerState;
static int strcasecmp_ext(const char *a, const char *b)
@@ -194,6 +194,27 @@ static int total_speaking_portions(PlayerState *s)
return s->silenceCount + 1;
}
+/* Get the seek target (in seconds) for jumping to a speaking portion.
+ Lands 2 render-frames (~33ms) into the padding zone. */
+static float segment_seek_target(PlayerState *s, int portion)
+{
+ float pos = speaking_portion_start(s, portion);
+ float target = pos * s->duration + (2.0f / 60.0f);
+ if (target < 0.0f) target = 0.0f;
+ if (target > s->duration) target = s->duration;
+ return target;
+}
+
+/* Check if normalized position is in the padding zone of a speaking portion.
+ The padding zone is [speaking_start, speaking_start + 0.25s/duration]. */
+static bool in_padding_zone(PlayerState *s, float pos, int portion)
+{
+ if (s->duration <= 0.0f) return false;
+ float padNorm = 0.25f / s->duration;
+ float start = speaking_portion_start(s, portion);
+ return (pos >= start && pos < start + padNorm);
+}
+
static void draw_text_centered(Font font, const char *text, float y, float fontSize, Color color)
{
float spacing = fontSize * 0.03f;
@@ -291,6 +312,7 @@ int main(void)
PlayerState state = { 0 };
state.studyMode = true;
+ state.lastSilenceIdx = -1;
/* Layout constants */
const float titleY = 60.0f;
@@ -301,7 +323,8 @@ int main(void)
const float statusY = barY + barHeight + 30.0f;
const float btnY = statusY + 120.0f + 55.0f; /* below status text + gap */
const float btnRadius = 55.0f;
- const float btnSpacing = 150.0f;
+ const float btnSpacing = 150.0f; /* currently unused */
+ (void)btnSpacing;
const float btnCenterX = SCREEN_W / 2.0f;
const float helpY = SCREEN_H - 80.0f;
@@ -351,127 +374,122 @@ int main(void)
/* --- Button clicks --- */
if (state.loaded)
{
- /* Seek back button */
+ /* Seek back button (disabled) */
+#if 0
if (button_hit(btnCenterX - btnSpacing, btnY, btnRadius))
{
seek_to(&state, state.currentTime - 5.0f);
- state.pausedAtSilence = false;
}
+#endif
- /* Play/pause button */
+ /* Play/pause button: acts like c (if playing) or n (if paused) */
if (button_hit(btnCenterX, btnY, btnRadius))
{
if (state.playing)
{
PauseMusicStream(state.music);
- if (state.studyMode)
- {
- /* Jump to start of current speaking part */
- float pos = state.currentTime / state.duration;
- int portion = current_speaking_portion(&state, pos);
- float target = speaking_portion_start(&state, portion) * state.duration;
- SeekMusicStream(state.music, target);
- state.currentTime = target;
- }
- else
- {
- float rewind = state.currentTime - 1.0f;
- if (rewind < 0.0f) rewind = 0.0f;
- SeekMusicStream(state.music, rewind);
- state.currentTime = rewind;
- }
state.playing = false;
- state.pausedAtSilence = false;
}
else
{
- if (state.studyMode && state.pausedAtSilence)
- {
- /* Resume after the silence gap */
- float pos = state.currentTime / state.duration;
- int silIdx = find_silence_at(&state, pos);
- if (silIdx >= 0)
- {
- float target = state.silence[silIdx].end * state.duration;
- SeekMusicStream(state.music, target);
- state.currentTime = target;
- }
- state.pausedAtSilence = false;
- }
ResumeMusicStream(state.music);
state.playing = true;
}
}
- /* Seek forward button */
+ /* Seek forward button (disabled) */
+#if 0
if (button_hit(btnCenterX + btnSpacing, btnY, btnRadius))
{
seek_to(&state, state.currentTime + 5.0f);
- state.pausedAtSilence = false;
}
- }
+#endif
- /* --- Keyboard input --- */
- if (state.loaded && IsKeyPressed(KEY_SPACE))
- {
- if (state.playing)
+ /* Section nav buttons (prev/next section, same as V/B) */
{
- PauseMusicStream(state.music);
- if (state.studyMode)
+ float progress = (state.duration > 0.0f) ? state.currentTime / state.duration : 0.0f;
+ int portion = current_speaking_portion(&state, progress) + 1;
+ int total = total_speaking_portions(&state);
+ if (portion > total) portion = total;
+ char portionBuf[32];
+ snprintf(portionBuf, sizeof(portionBuf), "%d/%d", portion, total);
+ float portionY = btnY + btnRadius + 80;
+ float portionSpacing = szSmall * 0.03f;
+ Vector2 portionSize = MeasureTextEx(fontSmall, portionBuf, szSmall, portionSpacing);
+ float portionX = (SCREEN_W - portionSize.x) / 2.0f;
+ float secBtnRadius = 35.0f;
+ float secBtnGap = 30.0f;
+ float secPrevX = portionX - secBtnGap - secBtnRadius;
+ float secNextX = portionX + portionSize.x + secBtnGap + secBtnRadius;
+ float secBtnY = portionY + szSmall / 2.0f;
+
+ if (button_hit(secPrevX, secBtnY, secBtnRadius))
{
float pos = state.currentTime / state.duration;
- int portion = current_speaking_portion(&state, pos);
- float target = speaking_portion_start(&state, portion) * state.duration;
- SeekMusicStream(state.music, target);
- state.currentTime = target;
- }
- else
- {
- float rewind = state.currentTime - 1.0f;
- if (rewind < 0.0f) rewind = 0.0f;
- SeekMusicStream(state.music, rewind);
- state.currentTime = rewind;
+ int p = current_speaking_portion(&state, pos);
+ bool inSil = (find_silence_at(&state, pos) >= 0);
+ bool inPad = in_padding_zone(&state, pos, p);
+ if ((inSil || inPad) && p > 0) p--;
+ float target = segment_seek_target(&state, p);
+ seek_to(&state, target);
+ state.wasInSilence = false;
+ state.lastSilenceIdx = -1;
}
- state.playing = false;
- state.pausedAtSilence = false;
- }
- else
- {
- if (state.studyMode && state.pausedAtSilence)
+ if (button_hit(secNextX, secBtnY, secBtnRadius))
{
- /* Skip past silence gap */
float pos = state.currentTime / state.duration;
- int silIdx = find_silence_at(&state, pos);
- if (silIdx >= 0)
- {
- float target = state.silence[silIdx].end * state.duration;
- SeekMusicStream(state.music, target);
- state.currentTime = target;
- }
- state.pausedAtSilence = false;
+ int p = current_speaking_portion(&state, pos);
+ if (p < total - 1) p++;
+ float target = segment_seek_target(&state, p);
+ seek_to(&state, target);
+ state.wasInSilence = false;
+ state.lastSilenceIdx = -1;
}
- ResumeMusicStream(state.music);
- state.playing = true;
}
}
- /* V = jump to start of current speaking part (double-tap for previous) */
+ /* --- Keyboard input --- */
+ /* C = pause only (does nothing if already paused) */
+ if (state.loaded && IsKeyPressed(KEY_C) && state.playing)
+ {
+ PauseMusicStream(state.music);
+ state.playing = false;
+ }
+
+ /* N = play only (does nothing if already playing); seeks to start of current speaking portion */
+ if (state.loaded && IsKeyPressed(KEY_N) && !state.playing)
+ {
+ float pos = state.currentTime / state.duration;
+ int portion = current_speaking_portion(&state, pos);
+ float target = segment_seek_target(&state, portion);
+ seek_to(&state, target);
+ ResumeMusicStream(state.music);
+ state.playing = true;
+ }
+
+ /* Space (held) = override study-mode auto-pause; resume if paused; never pauses */
+ if (state.loaded && IsKeyPressed(KEY_SPACE) && !state.playing)
+ {
+ ResumeMusicStream(state.music);
+ state.playing = true;
+ }
+
+ /* V = jump to current or previous speaking portion */
if (state.loaded && IsKeyPressed(KEY_V))
{
float pos = state.currentTime / state.duration;
int portion = current_speaking_portion(&state, pos);
- double now = GetTime();
- bool doubleTap = (now - state.lastVPress) < 0.5;
- state.lastVPress = now;
- if (doubleTap && portion > 0)
- {
+ /* If in silence or in the padding zone of current portion, go to previous */
+ bool inSil = (find_silence_at(&state, pos) >= 0);
+ bool inPad = in_padding_zone(&state, pos, portion);
+ if ((inSil || inPad) && portion > 0)
portion--;
- }
- float target = speaking_portion_start(&state, portion) * state.duration;
+
+ float target = segment_seek_target(&state, portion);
seek_to(&state, target);
- state.pausedAtSilence = false;
state.wasInSilence = false;
+ state.lastSilenceIdx = -1;
}
/* B = jump to start of next speaking part */
@@ -481,10 +499,10 @@ int main(void)
int portion = current_speaking_portion(&state, pos);
int total = total_speaking_portions(&state);
if (portion < total - 1) portion++;
- float target = speaking_portion_start(&state, portion) * state.duration;
+ float target = segment_seek_target(&state, portion);
seek_to(&state, target);
- state.pausedAtSilence = false;
state.wasInSilence = false;
+ state.lastSilenceIdx = -1;
}
/* --- 2.3 Click-to-seek on progress bar --- */
@@ -555,16 +573,33 @@ int main(void)
if (state.studyMode && state.duration > 0.0f)
{
float pos = state.currentTime / state.duration;
- bool nowInSilence = (find_silence_at(&state, pos) >= 0);
+ int silIdx = find_silence_at(&state, pos);
+ bool nowInSilence = (silIdx >= 0);
if (nowInSilence && !state.wasInSilence && !IsKeyDown(KEY_SPACE))
{
- /* Just entered silence — auto-pause */
+ /* Just entered silence — auto-pause and jump to next segment */
+ float target = segment_seek_target(&state, silIdx + 1);
+ PauseMusicStream(state.music);
+ SeekMusicStream(state.music, target);
+ state.currentTime = target;
+ state.playing = false;
+ state.skipAutoUpdate = 3;
+ }
+ else if (!nowInSilence && state.wasInSilence && !IsKeyDown(KEY_SPACE))
+ {
+ /* Just exited silence into padding/speech — auto-pause here */
+ int portion = current_speaking_portion(&state, pos);
+ float target = segment_seek_target(&state, portion);
PauseMusicStream(state.music);
+ SeekMusicStream(state.music, target);
+ state.currentTime = target;
state.playing = false;
- state.pausedAtSilence = true;
+ state.skipAutoUpdate = 3;
}
+
state.wasInSilence = nowInSilence;
+ if (nowInSilence) state.lastSilenceIdx = silIdx;
}
}
}
@@ -622,13 +657,22 @@ int main(void)
/* --- Buttons --- */
Vector2 mousePos = GetMousePosition();
- /* Seek back button */
+ /* Seek back button (disabled) */
+#if 0
float sbx = btnCenterX - btnSpacing;
float sdx1 = mousePos.x - sbx, sdy1 = mousePos.y - btnY;
bool hoverBack = (sdx1*sdx1 + sdy1*sdy1) <= (btnRadius*btnRadius);
DrawCircle((int)sbx, (int)btnY, btnRadius, (Color){ 50, 50, 70, 255 });
if (hoverBack) DrawCircle((int)sbx, (int)btnY, btnRadius, btnHoverColor);
draw_seek_back_icon(sbx, btnY, 50, textColor);
+ {
+ float lbl_spacing = szHelp * 0.03f;
+ const char *lbl = "5";
+ float lblFontSize = szHelp * 0.8f;
+ Vector2 lblSz = MeasureTextEx(fontHelp, lbl, lblFontSize, lbl_spacing);
+ DrawTextEx(fontHelp, lbl, (Vector2){ sbx - lblSz.x/2, btnY - lblSz.y/2 }, lblFontSize, lbl_spacing, (Color){ 50, 50, 70, 255 });
+ }
+#endif
/* Play/pause button — color reflects silence/speech */
Color playBtnColor = (state.playing && inSilence) ? (Color){ 160, 40, 55, 255 } : accentColor;
@@ -642,22 +686,56 @@ int main(void)
else
draw_play_icon(ppx, btnY, 50, textColor);
- /* Seek forward button */
+ /* Seek forward button (disabled) */
+#if 0
float sfx = btnCenterX + btnSpacing;
float sdx2 = mousePos.x - sfx, sdy2 = mousePos.y - btnY;
bool hoverFwd = (sdx2*sdx2 + sdy2*sdy2) <= (btnRadius*btnRadius);
DrawCircle((int)sfx, (int)btnY, btnRadius, (Color){ 50, 50, 70, 255 });
if (hoverFwd) DrawCircle((int)sfx, (int)btnY, btnRadius, btnHoverColor);
draw_seek_fwd_icon(sfx, btnY, 50, textColor);
+ {
+ float lbl_spacing = szHelp * 0.03f;
+ const char *lbl = "5";
+ float lblFontSize = szHelp * 0.8f;
+ Vector2 lblSz = MeasureTextEx(fontHelp, lbl, lblFontSize, lbl_spacing);
+ DrawTextEx(fontHelp, lbl, (Vector2){ sfx - lblSz.x/2, btnY - lblSz.y/2 }, lblFontSize, lbl_spacing, (Color){ 50, 50, 70, 255 });
+ }
+#endif
- /* Speaking portion counter below buttons */
+ /* Speaking portion counter with prev/next section buttons */
{
int portion = current_speaking_portion(&state, progress) + 1;
int total = total_speaking_portions(&state);
if (portion > total) portion = total;
char portionBuf[32];
snprintf(portionBuf, sizeof(portionBuf), "%d/%d", portion, total);
- draw_text_centered(fontSmall, portionBuf, btnY + btnRadius + 80, szSmall, mutedColor);
+ float portionY = btnY + btnRadius + 80;
+ float portionSpacing = szSmall * 0.03f;
+ Vector2 portionSize = MeasureTextEx(fontSmall, portionBuf, szSmall, portionSpacing);
+ float portionX = (SCREEN_W - portionSize.x) / 2.0f;
+ DrawTextEx(fontSmall, portionBuf, (Vector2){ portionX, portionY }, szSmall, portionSpacing, mutedColor);
+
+ /* Section nav buttons */
+ float secBtnRadius = 35.0f;
+ float secBtnGap = 30.0f;
+ float secPrevX = portionX - secBtnGap - secBtnRadius;
+ float secNextX = portionX + portionSize.x + secBtnGap + secBtnRadius;
+ float secBtnY = portionY + szSmall / 2.0f;
+
+ /* Prev section button */
+ float sd3 = mousePos.x - secPrevX, sd4 = mousePos.y - secBtnY;
+ bool hoverSecPrev = (sd3*sd3 + sd4*sd4) <= (secBtnRadius*secBtnRadius);
+ DrawCircle((int)secPrevX, (int)secBtnY, secBtnRadius, (Color){ 50, 50, 70, 255 });
+ if (hoverSecPrev) DrawCircle((int)secPrevX, (int)secBtnY, secBtnRadius, btnHoverColor);
+ draw_seek_back_icon(secPrevX, secBtnY, 30, textColor);
+
+ /* Next section button */
+ float sd5 = mousePos.x - secNextX, sd6 = mousePos.y - secBtnY;
+ bool hoverSecNext = (sd5*sd5 + sd6*sd6) <= (secBtnRadius*secBtnRadius);
+ DrawCircle((int)secNextX, (int)secBtnY, secBtnRadius, (Color){ 50, 50, 70, 255 });
+ if (hoverSecNext) DrawCircle((int)secNextX, (int)secBtnY, secBtnRadius, btnHoverColor);
+ draw_seek_fwd_icon(secNextX, secBtnY, 30, textColor);
}
}
else
@@ -672,7 +750,7 @@ int main(void)
{
float helpSpacing = szHelp * 0.03f;
float padding = 40.0f;
- DrawTextEx(fontHelp, "Space: play/pause Arrows: seek V/B: prev/next section 0-9: jump",
+ DrawTextEx(fontHelp, "C: pause N: play Space(hold): override V/B: prev/next Arrows: seek 0-9: jump",
(Vector2){ padding, helpY }, szHelp, helpSpacing, mutedColor);
/* Study mode checkbox - right aligned on same line */