klaudio 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klaudio",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Add sound effects to your coding sessions — play sounds when tasks complete, notifications arrive, and more",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -3,7 +3,7 @@ import { render, Box, Text, useInput, useApp } from "ink";
3
3
  import Spinner from "ink-spinner";
4
4
  import { PRESETS, EVENTS } from "./presets.js";
5
5
  import { playSoundWithCancel, getWavDuration } from "./player.js";
6
- import { getAvailableGames } from "./scanner.js";
6
+ import { getAvailableGames, getSystemSounds } from "./scanner.js";
7
7
  import { install, uninstall, getExistingSounds } from "./installer.js";
8
8
  import { getVgmstreamPath, findPackedAudioFiles, extractToWav } from "./extractor.js";
9
9
  import { extractUnityResource } from "./unity.js";
@@ -160,6 +160,7 @@ const PresetScreen = ({ onNext, onBack }) => {
160
160
  label: `${p.icon} ${p.name} — ${p.description}`,
161
161
  value: id,
162
162
  })),
163
+ { label: "🔔 System sounds — use built-in OS notification sounds", value: "_system" },
163
164
  { label: "🕹️ Scan local games — find sounds from your Steam & Epic Games library", value: "_scan" },
164
165
  { label: "📁 Custom files — provide your own sound files", value: "_custom" },
165
166
  ];
@@ -757,6 +758,16 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
757
758
 
758
759
  // Phase 1: Browse and pick files (auto-preview plays on highlight)
759
760
  const filterLower = filter.toLowerCase();
761
+
762
+ // Parse duration filter: "10s", "<10s", "< 10s", ">5s", "> 5s", "<=10s", ">=5s"
763
+ const durationFilter = useMemo(() => {
764
+ const m = filter.match(/^\s*(<|>|<=|>=)?\s*(\d+(?:\.\d+)?)\s*s\s*$/);
765
+ if (!m) return null;
766
+ const op = m[1] || "<=";
767
+ const val = parseFloat(m[2]);
768
+ return { op, val };
769
+ }, [filter]);
770
+
760
771
  const allFileItems = categoryFiles.map((f) => {
761
772
  const dur = fileDurations[f.path];
762
773
  const durStr = dur != null ? ` (${dur}s${dur > MAX_PLAY_SECONDS ? `, preview ${MAX_PLAY_SECONDS}s` : ""})` : "";
@@ -768,11 +779,22 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
768
779
  label: `${catTag}${name}${durStr}`,
769
780
  usedTag: usedFor ? ` ← ${usedFor.join(", ")}` : null,
770
781
  value: f.path,
782
+ _dur: dur,
771
783
  };
772
784
  });
773
785
 
774
786
  const filteredFiles = filter
775
- ? allFileItems.filter((i) => i.label.toLowerCase().includes(filterLower))
787
+ ? durationFilter
788
+ ? allFileItems.filter((i) => {
789
+ if (i._dur == null) return false;
790
+ const { op, val } = durationFilter;
791
+ if (op === "<") return i._dur < val;
792
+ if (op === ">") return i._dur > val;
793
+ if (op === "<=") return i._dur <= val;
794
+ if (op === ">=") return i._dur >= val;
795
+ return true;
796
+ })
797
+ : allFileItems.filter((i) => i.label.toLowerCase().includes(filterLower))
776
798
  : allFileItems;
777
799
 
778
800
  const fileItems = [
@@ -797,12 +819,12 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
797
819
  ),
798
820
  filter
799
821
  ? h(Box, { marginLeft: 4 },
800
- h(Text, { color: "yellow" }, "Filter: "),
822
+ h(Text, { color: "yellow" }, durationFilter ? "Duration: " : "Filter: "),
801
823
  h(Text, { bold: true }, filter),
802
824
  h(Text, { dimColor: true }, ` (${filteredFiles.length} match${filteredFiles.length !== 1 ? "es" : ""})`),
803
825
  )
804
826
  : categoryFiles.length > 15
805
- ? h(Text, { dimColor: true, marginLeft: 4 }, "Type to filter...")
827
+ ? h(Text, { dimColor: true, marginLeft: 4 }, "Type to filter... (e.g. <10s, >5s)")
806
828
  : null,
807
829
  fileItems.length > 0
808
830
  ? h(Box, { marginLeft: 2 },
@@ -1417,6 +1439,12 @@ const InstallApp = () => {
1417
1439
  onNext: (id) => {
1418
1440
  if (id === "_music") {
1419
1441
  setScreen(SCREEN.MUSIC_MODE);
1442
+ } else if (id === "_system") {
1443
+ getSystemSounds().then((files) => {
1444
+ const catFiles = categorizeLooseFiles(files);
1445
+ setSelectedGame({ name: "System Sounds", path: "", files: catFiles, fileCount: catFiles.length, hasAudio: catFiles.length > 0 });
1446
+ setScreen(SCREEN.GAME_SOUNDS);
1447
+ });
1420
1448
  } else if (id === "_scan") {
1421
1449
  setScreen(SCREEN.GAME_PICK);
1422
1450
  } else if (id === "_custom") {
package/src/scanner.js CHANGED
@@ -379,6 +379,46 @@ export async function scanForGames(onProgress, onGameFound) {
379
379
  return games;
380
380
  }
381
381
 
382
+ /**
383
+ * Discover system sounds (Windows Media, macOS system sounds, Linux sound themes).
384
+ * Returns an array of { path, name, dir } matching the game file format.
385
+ */
386
+ export async function getSystemSounds() {
387
+ const os = platform();
388
+ const results = [];
389
+
390
+ const dirs = [];
391
+ if (os === "win32") {
392
+ dirs.push("C:/Windows/Media", "C:/Windows/Media/dm");
393
+ } else if (os === "darwin") {
394
+ dirs.push("/System/Library/Sounds");
395
+ } else {
396
+ // Linux: common sound theme locations
397
+ dirs.push(
398
+ "/usr/share/sounds/freedesktop/stereo",
399
+ "/usr/share/sounds/gnome/default/alerts",
400
+ "/usr/share/sounds/ubuntu/stereo",
401
+ "/usr/share/sounds",
402
+ );
403
+ }
404
+
405
+ for (const dir of dirs) {
406
+ if (!(await dirExists(dir))) continue;
407
+ try {
408
+ const entries = await readdir(dir, { withFileTypes: true });
409
+ for (const entry of entries) {
410
+ if (!entry.isFile()) continue;
411
+ const ext = extname(entry.name).toLowerCase();
412
+ if (AUDIO_EXTENSIONS.has(ext)) {
413
+ results.push({ path: join(dir, entry.name), name: entry.name, dir });
414
+ }
415
+ }
416
+ } catch { /* skip */ }
417
+ }
418
+
419
+ return results;
420
+ }
421
+
382
422
  /**
383
423
  * Get a flat list of all found game directories.
384
424
  */