+ {/* Zoom and Pan Controls */}
+
+
+
+ Zoom: {zoom.toFixed(1)}x
+ {zoom > 1 && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+ {/* Playback Controls */}
+
+
+
+
+ Start: {startTime}s | Duration: {duration}s | Total: {Math.floor(audioDuration)}s
+
+
+
+ {/* Segment Playback Buttons */}
+
+ Play Segments:
+ {unlockSteps.map((step, index) => {
+ const segmentStart = index > 0 ? unlockSteps[index - 1] : 0;
+ const segmentDuration = step - segmentStart;
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/lib/dailyPuzzle.ts b/lib/dailyPuzzle.ts
index da46eab..fa76269 100644
--- a/lib/dailyPuzzle.ts
+++ b/lib/dailyPuzzle.ts
@@ -146,32 +146,36 @@ export async function getOrCreateSpecialPuzzle(specialName: string) {
});
if (!dailyPuzzle) {
- // Get songs available for this special
- const allSongs = await prisma.song.findMany({
- where: { specials: { some: { id: special.id } } },
+ // Get songs available for this special through SpecialSong
+ const specialSongs = await prisma.specialSong.findMany({
+ where: { specialId: special.id },
include: {
- puzzles: {
- where: { specialId: special.id }
- },
- },
+ song: {
+ include: {
+ puzzles: {
+ where: { specialId: special.id }
+ }
+ }
+ }
+ }
});
- if (allSongs.length === 0) return null;
+ if (specialSongs.length === 0) return null;
// Calculate weights
- const weightedSongs = allSongs.map(song => ({
- song,
- weight: 1.0 / (song.puzzles.length + 1),
+ const weightedSongs = specialSongs.map(specialSong => ({
+ specialSong,
+ weight: 1.0 / (specialSong.song.puzzles.length + 1),
}));
const totalWeight = weightedSongs.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
- let selectedSong = weightedSongs[0].song;
+ let selectedSpecialSong = weightedSongs[0].specialSong;
for (const item of weightedSongs) {
random -= item.weight;
if (random <= 0) {
- selectedSong = item.song;
+ selectedSpecialSong = item.specialSong;
break;
}
}
@@ -180,7 +184,7 @@ export async function getOrCreateSpecialPuzzle(specialName: string) {
dailyPuzzle = await prisma.dailyPuzzle.create({
data: {
date: today,
- songId: selectedSong.id,
+ songId: selectedSpecialSong.songId,
specialId: special.id
},
include: { song: true },
@@ -198,6 +202,16 @@ export async function getOrCreateSpecialPuzzle(specialName: string) {
if (!dailyPuzzle) return null;
+ // Fetch the startTime from SpecialSong
+ const specialSong = await prisma.specialSong.findUnique({
+ where: {
+ specialId_songId: {
+ specialId: special.id,
+ songId: dailyPuzzle.songId
+ }
+ }
+ });
+
// Calculate puzzle number
const puzzleCount = await prisma.dailyPuzzle.count({
where: {
@@ -218,7 +232,8 @@ export async function getOrCreateSpecialPuzzle(specialName: string) {
coverImage: dailyPuzzle.song.coverImage ? `/uploads/covers/${dailyPuzzle.song.coverImage}` : null,
special: specialName,
maxAttempts: special.maxAttempts,
- unlockSteps: JSON.parse(special.unlockSteps)
+ unlockSteps: JSON.parse(special.unlockSteps),
+ startTime: specialSong?.startTime || 0
};
} catch (error) {
diff --git a/prisma/migrations/20251123012308_add_special_song_model/migration.sql b/prisma/migrations/20251123012308_add_special_song_model/migration.sql
new file mode 100644
index 0000000..89fb071
--- /dev/null
+++ b/prisma/migrations/20251123012308_add_special_song_model/migration.sql
@@ -0,0 +1,21 @@
+-- CreateTable
+CREATE TABLE "SpecialSong" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "specialId" INTEGER NOT NULL,
+ "songId" INTEGER NOT NULL,
+ "startTime" INTEGER NOT NULL DEFAULT 0,
+ "order" INTEGER,
+ CONSTRAINT "SpecialSong_specialId_fkey" FOREIGN KEY ("specialId") REFERENCES "Special" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT "SpecialSong_songId_fkey" FOREIGN KEY ("songId") REFERENCES "Song" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+-- Migrate data from _SongToSpecial to SpecialSong
+INSERT INTO "SpecialSong" ("specialId", "songId", "startTime")
+SELECT "B" as "specialId", "A" as "songId", 0 as "startTime"
+FROM "_SongToSpecial";
+
+-- DropTable
+DROP TABLE "_SongToSpecial";
+
+-- CreateIndex
+CREATE UNIQUE INDEX "SpecialSong_specialId_songId_key" ON "SpecialSong"("specialId", "songId");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index d14e0e7..b46456f 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -19,7 +19,7 @@ model Song {
createdAt DateTime @default(now())
puzzles DailyPuzzle[]
genres Genre[]
- specials Special[]
+ specials SpecialSong[]
}
model Genre {
@@ -35,10 +35,22 @@ model Special {
maxAttempts Int @default(7)
unlockSteps String // JSON array: "[2,4,7,11,16,30,60]"
createdAt DateTime @default(now())
- songs Song[]
+ songs SpecialSong[]
dailyPuzzles DailyPuzzle[]
}
+model SpecialSong {
+ id Int @id @default(autoincrement())
+ specialId Int
+ special Special @relation(fields: [specialId], references: [id], onDelete: Cascade)
+ songId Int
+ song Song @relation(fields: [songId], references: [id], onDelete: Cascade)
+ startTime Int @default(0) // Start time in seconds
+ order Int? // For manual ordering
+
+ @@unique([specialId, songId])
+}
+
model DailyPuzzle {
id Int @id @default(autoincrement())
date String // Format: YYYY-MM-DD