@ihabdevteam/core 0.124.0 → 0.125.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.
@@ -1341,7 +1341,7 @@ export declare function copyText(text: string): Promise<boolean>;
1341
1341
  *
1342
1342
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
1343
1343
  */
1344
- export declare const CORE_VERSION = "0.124.0";
1344
+ export declare const CORE_VERSION = "0.125.0";
1345
1345
 
1346
1346
  /** @public */
1347
1347
  declare const coreDefault: Record<string, unknown>;
@@ -1363,13 +1363,13 @@ export declare type CoreNoiseSettings = {
1363
1363
  level: number;
1364
1364
  /**
1365
1365
  * 소음 비율 — 문제 소리 대비 소음의 배수, 0–2.0(0.1 칸). 1 = 소음과 문제 소리가 같은 크기.
1366
- * 문제 소리 = mainVolume × min(1, 1/noiseRatio), 소음 = mainVolume × min(1, noiseRatio).
1366
+ * 문제 소리 = mainVolume, 소음 들리는 크기 = mainVolume × noiseRatio(원본 소음 파일 기준).
1367
1367
  * 없으면 `level` 에서 옮긴다.
1368
1368
  */
1369
1369
  noiseRatio?: number;
1370
1370
  /** 소음 재생 속도(playbackRate). 0.75–1.25, 기본 1. */
1371
1371
  speed: number;
1372
- /** 전체 소리 — 두 소리 중 큰 쪽의 크기. 0–1, 기본 1. 실제 두 볼륨은 `noiseRatio` 로 나뉜다. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
1372
+ /** 전체 소리 = 문제 소리 볼륨. 0–1, 기본 1. 소음은 이 값 × `noiseRatio`. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
1373
1373
  mainVolume: number;
1374
1374
  /** 문장(메인) 재생 속도(playbackRate). 0.5–1.5, 기본 1. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
1375
1375
  mainSpeed: number;
@@ -1417,6 +1417,8 @@ export declare type CoreScopeStorageConfig = {
1417
1417
  * @example 'https://cdn.example.com/sounds/noise'
1418
1418
  */
1419
1419
  noiseBaseUrl?: string;
1420
+ /** (선택) 소음 파일이 원본보다 몇 배 큰지(진폭). 기본 1 — configureStorage 의 같은 이름 참고. */
1421
+ noiseFileGain?: number;
1420
1422
  };
1421
1423
 
1422
1424
  /**
@@ -2269,6 +2271,12 @@ export declare function getImageResolver(): ((word: string, homonum?: number) =>
2269
2271
  */
2270
2272
  export declare function getNoiseBaseUrl(): string;
2271
2273
 
2274
+ /**
2275
+ * 소음 파일이 원본보다 몇 배 큰지 — configureStorage 의 noiseFileGain. 없거나 양수가 아니면 1.
2276
+ * @public
2277
+ */
2278
+ export declare function getNoiseFileGain(): number;
2279
+
2272
2280
  /** @public 현재 메모리에 적재된 인덱스 (없으면 localStorage 시도) */
2273
2281
  export declare function getPairIndex(): PairIndex | null;
2274
2282
 
@@ -5650,6 +5658,16 @@ export declare interface StorageResolvers {
5650
5658
  * @example 'https://cdn.example.com/sounds/noise'
5651
5659
  */
5652
5660
  noiseBaseUrl?: string;
5661
+ /**
5662
+ * (선택) `noiseBaseUrl` 의 소음 파일이 **원본 소음보다 몇 배 큰지**(진폭). 기본 1.
5663
+ *
5664
+ * 소음 비율 R 은 원본 파일 기준이라 `<audio>` 볼륨 = min(1, 전체 소리 × R ÷ 이 값) 이다.
5665
+ * 볼륨은 1 을 넘을 수 없으므로 원본 파일로는 1배 넘는 소음이 1 에서 멈춘다 — 2배로 키운 파일과
5666
+ * `noiseFileGain: 2` 를 함께 주면 0~2배가 정확히 들린다.
5667
+ *
5668
+ * @example 2
5669
+ */
5670
+ noiseFileGain?: number;
5653
5671
  }
5654
5672
 
5655
5673
  /** @public */
@@ -20,6 +20,8 @@ export type CoreScopeStorageConfig = {
20
20
  * @example 'https://cdn.example.com/sounds/noise'
21
21
  */
22
22
  noiseBaseUrl?: string;
23
+ /** (선택) 소음 파일이 원본보다 몇 배 큰지(진폭). 기본 1 — configureStorage 의 같은 이름 참고. */
24
+ noiseFileGain?: number;
23
25
  };
24
26
  /** @public */
25
27
  export type CoreScopeProps = {
@@ -11,6 +11,7 @@ export function CoreScope({ as: Component = 'div', className = '', children, sto
11
11
  exists: storage.exists,
12
12
  resolvePath: storage.resolvePath,
13
13
  noiseBaseUrl: storage.noiseBaseUrl,
14
+ noiseFileGain: storage.noiseFileGain,
14
15
  });
15
16
  }
16
17
  const classes = cx('ihabdevteam-core-scope', className);
@@ -21,10 +21,18 @@ export declare function noiseRatioOf(settings: Pick<CoreNoiseSettings, 'level' |
21
21
  type 비율설정 = Pick<CoreNoiseSettings, 'level' | 'mainVolume'> & {
22
22
  noiseRatio?: number;
23
23
  };
24
- /** @public 실제 소음 볼륨(0~1) = mainVolume × min(1, 비율). 켜짐 여부는 보지 않는다. */
25
- export declare function effectiveNoiseVolume(settings: 비율설정): number;
26
- /** @public 실제 문제 소리 볼륨(0~1) = mainVolume × min(1, 1/비율). 비율 0 이면 mainVolume. */
24
+ /** @public 문제 소리 볼륨(0~1) = mainVolume. 비율과 상관없다. */
27
25
  export declare function effectiveSpeechVolume(settings: 비율설정): number;
26
+ /**
27
+ * @public 소음이 **들리는 크기**(0~2, 원본 소음 파일 기준) = mainVolume × 비율. 켜짐 여부는 보지
28
+ * 않는다. 결과 기록·파형이 쓴다. 소음 `<audio>` 에 거는 값은 `noiseElementVolume`.
29
+ */
30
+ export declare function effectiveNoiseVolume(settings: 비율설정): number;
31
+ /**
32
+ * @public 소음 `<audio>` 볼륨(0~1) = min(1, 들리는 크기 ÷ gain). gain 은 소음 파일이 원본보다 몇 배
33
+ * 큰지 — 기본은 `configureStorage({ noiseFileGain })`(없으면 1).
34
+ */
35
+ export declare function noiseElementVolume(settings: 비율설정, gain?: number): number;
28
36
  /**
29
37
  * @public 설정에 조각을 덮는다 — `{ ...base, ...patch }` 에 비율 규칙 하나를 더한 것.
30
38
  *
@@ -32,8 +40,8 @@ export declare function effectiveSpeechVolume(settings: 비율설정): number;
32
40
  * 옛 `noiseOverride`). 그대로 덮으면 base 의 `noiseRatio`(기본값 1 등)가 남아 level 을 가려
33
41
  * 버리므로, 덮은 결과의 level·mainVolume 으로 비율을 셈해 **그 자리에서 박아 둔다** —
34
42
  * 박지 않고 비워 두면 뒤에 전체 소리만 바꿀 때 비율이 따라 변해 소음이 옛날처럼 제자리에 선다.
35
- * 비율이 1 을 넘으면 소음이 큰 쪽이므로 `mainVolume` 도 level/9 로 함께 박는다(옛날에 들리던
36
- * 두 크기 그대로 — 예: level 9·mainVolume 0.5 → 비율 2·mainVolume 1 = 소음 1·문제 0.5).
43
+ * `mainVolume` 은 그대로다 — 옛 뜻(문제 소리 = mainVolume, 소음 = level/9)이 새 셈(문제 = M, 소음 = M×R)
44
+ * 에서 들리던 크기 그대로 남는다(0.123.0 의 "R>1 이면 mainVolume = level/9" 는 0.125.0 에서 걷음).
37
45
  * `{ ...DEFAULT_NOISE_SETTINGS, ...saved }` 대신 이것을 쓴다.
38
46
  */
39
47
  export declare function mergeNoiseSettings(base: CoreNoiseSettings, patch?: Partial<CoreNoiseSettings> | null): CoreNoiseSettings;
@@ -50,7 +58,7 @@ export type NoisePlayerContextValue = {
50
58
  /** 문장(메인) 재생 속도(playbackRate). 0.5–1.5, 기본 1. */
51
59
  playbackRate: number;
52
60
  setPlaybackRate: (rate: number) => void;
53
- /** 전체 소리 — 두 소리(문제 소리·소음) 중 큰 쪽의 크기. 0–1, 기본 1. 실제 볼륨은 effectiveSpeechVolume·effectiveNoiseVolume. */
61
+ /** 전체 소리 = 문제 소리 볼륨. 0–1, 기본 1. 소음은 이 값 × 소음 비율(noiseElementVolume 참고). */
54
62
  mainVolume: number;
55
63
  setMainVolume: (v: number) => void;
56
64
  };
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { createContext, useContext } from 'react';
3
+ import { getNoiseFileGain } from '../../utils/storage';
3
4
  // 소음 크기: 내부 level(0~9 연속) ↔ 표시 percent(0~100). 소음 설정 툴팁과 계정 설정("훈련" 패널)이
4
5
  // 동일한 매핑을 써야 하므로 여기서 한 번만 정의해 공유한다.
5
6
  // 소음 음원 파일을 클리핑 없는 한도(+7dB, 약 2.24배)까지 증폭한 뒤 맞춘 상한값 —
@@ -11,15 +12,16 @@ export const levelToPct = (level) => Math.round((Math.min(LEVEL_MAX, Math.max(0,
11
12
  /** @public */
12
13
  export const pctToLevel = (pct) => (pct / 100) * LEVEL_MAX;
13
14
  /* ── 소음 비율 ──────────────────────────────────────────────────────────────
14
- 전체 소리(`mainVolume` 0~1)는 **두 소리 중 큰 쪽의 크기**이고, 소음은 문제 소리에 대한
15
- **비율**(`noiseRatio` R, 0~2배)로 정한다.
16
- R ≤ 1: 문제 소리 = M, 소음 = M × R
17
- R > 1: 문제 소리 = M ÷ R, 소음 = M
18
- 곧 문제 소리 = M × min(1, 1/R), 소음 = M × min(1, R) — 어느 쪽도 1 을 넘지 않고, 0~2배가
19
- 전체 소리와 무관하게 정확히 들린다.
20
- (0.120.0 에서는 문제 소리 = M, 소음 = min(1, M×R) 이라 M 이 50% 를 넘으면 소음이 1 에 막혀
21
- 슬라이더 배수와 실제가 어긋났다 — 전체 80% 에 2배 → 실제 1.25배. 0.123.0 에서 고침.)
22
- 예전(0.119.0 까지)에는 소음 크기(level/9)가 전체 소리와 따로 움직였다. */
15
+ 전체 소리 M(`mainVolume` 0~1) = **문제 소리**. 소음은 문제 소리에 대한 **비율** R(`noiseRatio` 0~2배).
16
+ 문제 소리 = M — 비율과 상관없이 줄이지 않는다
17
+ 소음 들리는 크기 = M × R (0~2) — **원본 소음 파일 기준**
18
+ 소음 <audio> 볼륨 = min(1, M × R ÷ gain) — gain = 소음 파일이 원본보다 몇 배 큰지
19
+ (configureStorage 의 noiseFileGain, 기본 1)
20
+ <audio> 볼륨은 1 을 넘을 수 없어 원본 파일로는 1배 넘는 소음이 1 에서 멈춘다. 2배로 키운 파일
21
+ (gain 2)을 주면 0~2배가 정확히 들린다. 기본 gain 1 인 앱은 1배 초과만 1 에서 멈춘다.
22
+ 0.120.0: 문제 = M, 소음 = min(1, M×R) — M 이 크면 1 에 막힘.
23
+ 0.123.0: 문제 = M×min(1,1/R), 소음 = M×min(1,R) — 1~2배를 올려도 소음은 안 커지고 문제 소리만
24
+ 줄었다(말귀 사용자). 0.125.0 에서 키운 파일로 푼다. */
23
25
  /** @public 소음 비율 상한(배). */
24
26
  export const NOISE_RATIO_MAX = 2;
25
27
  /** @public 소음 비율 칸(배). */
@@ -41,29 +43,25 @@ export function noiseRatioOf(settings) {
41
43
  return 1;
42
44
  return 한칸반올림(비율자르기(소음 / 전체));
43
45
  }
46
+ const 전체소리 = (settings) => Math.min(1, Math.max(0, Number(settings.mainVolume) || 0));
47
+ /** @public 문제 소리 볼륨(0~1) = mainVolume. 비율과 상관없다. */
48
+ export function effectiveSpeechVolume(settings) {
49
+ return 전체소리(settings);
50
+ }
44
51
  /**
45
- * 전체 소리와 비율을 확정한다. `noiseRatio` 가 없는 옛 기록은 옛 뜻(문제 소리 = mainVolume,
46
- * 소음 = level/9)이 **들리던 크기 그대로** 남도록 옮긴다 — R = noiseRatioOf, 그리고 R > 1 이면
47
- * 소음이 큰 쪽이므로 전체 소리 = level/9.
52
+ * @public 소음이 **들리는 크기**(0~2, 원본 소음 파일 기준) = mainVolume × 비율. 켜짐 여부는 보지
53
+ * 않는다. 결과 기록·파형이 쓴다. 소음 `<audio>` 에 거는 값은 `noiseElementVolume`.
48
54
  */
49
- function 확정(settings) {
50
- const 전체 = Math.min(1, Math.max(0, Number(settings.mainVolume) || 0));
51
- const noiseRatio = noiseRatioOf(settings);
52
- const 옛기록 = !(typeof settings.noiseRatio === 'number' && Number.isFinite(settings.noiseRatio));
53
- if (옛기록 && noiseRatio > 1) {
54
- return { mainVolume: Math.min(LEVEL_MAX, Math.max(0, Number(settings.level) || 0)) / LEVEL_MAX, noiseRatio };
55
- }
56
- return { mainVolume: 전체, noiseRatio };
57
- }
58
- /** @public 실제 소음 볼륨(0~1) = mainVolume × min(1, 비율). 켜짐 여부는 보지 않는다. */
59
55
  export function effectiveNoiseVolume(settings) {
60
- const { mainVolume, noiseRatio } = 확정(settings);
61
- return mainVolume * Math.min(1, noiseRatio);
56
+ return 전체소리(settings) * noiseRatioOf(settings);
62
57
  }
63
- /** @public 실제 문제 소리 볼륨(0~1) = mainVolume × min(1, 1/비율). 비율 0 이면 mainVolume. */
64
- export function effectiveSpeechVolume(settings) {
65
- const { mainVolume, noiseRatio } = 확정(settings);
66
- return noiseRatio > 1 ? mainVolume / noiseRatio : mainVolume;
58
+ /**
59
+ * @public 소음 `<audio>` 볼륨(0~1) = min(1, 들리는 크기 ÷ gain). gain 은 소음 파일이 원본보다 몇 배
60
+ * 큰지 — 기본은 `configureStorage({ noiseFileGain })`(없으면 1).
61
+ */
62
+ export function noiseElementVolume(settings, gain = getNoiseFileGain()) {
63
+ const g = Number.isFinite(gain) && gain > 0 ? gain : 1;
64
+ return Math.min(1, effectiveNoiseVolume(settings) / g);
67
65
  }
68
66
  /**
69
67
  * @public 설정에 조각을 덮는다 — `{ ...base, ...patch }` 에 비율 규칙 하나를 더한 것.
@@ -72,16 +70,14 @@ export function effectiveSpeechVolume(settings) {
72
70
  * 옛 `noiseOverride`). 그대로 덮으면 base 의 `noiseRatio`(기본값 1 등)가 남아 level 을 가려
73
71
  * 버리므로, 덮은 결과의 level·mainVolume 으로 비율을 셈해 **그 자리에서 박아 둔다** —
74
72
  * 박지 않고 비워 두면 뒤에 전체 소리만 바꿀 때 비율이 따라 변해 소음이 옛날처럼 제자리에 선다.
75
- * 비율이 1 을 넘으면 소음이 큰 쪽이므로 `mainVolume` 도 level/9 로 함께 박는다(옛날에 들리던
76
- * 두 크기 그대로 — 예: level 9·mainVolume 0.5 → 비율 2·mainVolume 1 = 소음 1·문제 0.5).
73
+ * `mainVolume` 은 그대로다 — 옛 뜻(문제 소리 = mainVolume, 소음 = level/9)이 새 셈(문제 = M, 소음 = M×R)
74
+ * 에서 들리던 크기 그대로 남는다(0.123.0 의 "R>1 이면 mainVolume = level/9" 는 0.125.0 에서 걷음).
77
75
  * `{ ...DEFAULT_NOISE_SETTINGS, ...saved }` 대신 이것을 쓴다.
78
76
  */
79
77
  export function mergeNoiseSettings(base, patch) {
80
78
  const merged = { ...base, ...(patch ?? {}) };
81
79
  if (patch && 'level' in patch && !(typeof patch.noiseRatio === 'number')) {
82
- const 박을것 = 확정({ level: merged.level, mainVolume: merged.mainVolume });
83
- merged.noiseRatio = 박을것.noiseRatio;
84
- merged.mainVolume = 박을것.mainVolume;
80
+ merged.noiseRatio = noiseRatioOf({ level: merged.level, mainVolume: merged.mainVolume });
85
81
  }
86
82
  return merged;
87
83
  }
@@ -97,10 +97,10 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
97
97
  // 문장(메인) 재생 속도·볼륨 — 하단 소음설정 툴팁과 공유(NoisePlayerContext)
98
98
  const playbackRate = noisePlayer?.playbackRate ?? 1.0;
99
99
  const mainVolume = noisePlayer?.mainVolume ?? 1.0;
100
- // 실제 두 볼륨 — 전체 소리(큰 쪽 크기)를 소음 비율로 나눈다(소리용). 파형은 M·R 로 따로 그린다.
100
+ // 문제 소리 = 전체 소리. noiseVolume 은 소음이 들리는 크기(M×R, 원본 파일 기준) — 0 이면 소음 파형을 안 그린다.
101
+ // 소음 <audio> 볼륨(파일 gain 으로 나눈 값)은 useNoiseFade 가 건다.
101
102
  const noiseVolume = noisePlayer ? effectiveNoiseVolume(noisePlayer.settings) : 0;
102
- // 소음을 끈 판(소음 종류 "없음")은 들리지 않는 소음에 문제 소리를 양보하지 않는다 — 전체 소리 그대로.
103
- const speechVolume = noisePlayer?.settings.enabled ? effectiveSpeechVolume(noisePlayer.settings) : mainVolume;
103
+ const speechVolume = noisePlayer ? effectiveSpeechVolume(noisePlayer.settings) : mainVolume;
104
104
  const [fillCompletionPhase, setFillCompletionPhase] = useState('idle');
105
105
  const [noticeMessage, setNoticeMessage] = useState("");
106
106
  const [noticePosition] = useState("top");
@@ -143,7 +143,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
143
143
  const { startTTS, stopTTS } = useTTSPlayback({
144
144
  ttsText,
145
145
  playbackRate,
146
- // TTS 음성 = 문제 소리 — 전체 소리가 아니라 비율로 나뉜 문제 소리 볼륨
146
+ // TTS 음성 = 문제 소리 = 전체 소리
147
147
  mainVolume: speechVolume,
148
148
  fillRef,
149
149
  waveProgressRef,
@@ -950,8 +950,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
950
950
  }
951
951
  catch (e) { }
952
952
  }, [playbackRate]);
953
- // 문장(메인) 볼륨 반영 — 문제 소리 볼륨(effectiveSpeechVolume). 전체 소리·소음 비율 어느 쪽을
954
- // 바꿔도 달라진다. 재생 중이면 소음과 같은 150ms 램프로 옮기고, 멈춰 있으면 바로 건다.
953
+ // 문장(메인) 볼륨 반영 — 문제 소리 볼륨(effectiveSpeechVolume = 전체 소리). 재생 중이면 소음과 같은 150ms 램프로 옮기고, 멈춰 있으면 바로 건다.
955
954
  // 백그라운드 탭에서는 RAF 가 멈춰 램프가 중간에 설 수 있으므로 끝값을 타이머로 한 번 더 박는다.
956
955
  const speechRampRef = useRef({ raf: null, timer: null });
957
956
  useEffect(() => {
@@ -2,7 +2,7 @@
2
2
  import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { noiseAssets } from '../../sounds/noiseAssets';
4
4
  import { getElementAnalyser, resumeAudioMeter } from '../../utils/audioMeter';
5
- import { effectiveNoiseVolume } from './NoisePlayerContext';
5
+ import { noiseElementVolume } from './NoisePlayerContext';
6
6
  function generateNoiseUrl(type) {
7
7
  const rand = Math.floor(Math.random() * 10);
8
8
  switch (type) {
@@ -24,7 +24,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
24
24
  // 실제 로드된(재생되는) 소음 파일 URL — waveform이 화면=청취 일치하도록 이 src를 그린다.
25
25
  const [activeNoiseUrl, setActiveNoiseUrl] = useState(null);
26
26
  const noiseTypeLoadedRef = useRef(null);
27
- // volume = 실제 소음 볼륨(effectiveNoiseVolume — 전체 소리 × 소음 비율, 상한 1)
27
+ // volume = 소음 <audio> 볼륨(noiseElementVolume — 전체 소리 × 소음 비율 ÷ 파일 gain, 상한 1)
28
28
  const noiseSettingsRef = useRef(null);
29
29
  const noiseFadeRafRef = useRef(null);
30
30
  // 페이드 세대 토큰 — 하드스톱 타이머가 발화 시점에 "그 사이 새 페이드가 시작됐는지"를
@@ -157,7 +157,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
157
157
  // 개별 레이스를 전부 틀어막는 대신, 잘못된 상태를 주기적으로 감지해 회수한다.
158
158
  // 두 번 연속(약 0.8초) 확인 후에 정지 — 멀티소스 트랙 전환처럼 메인이 잠깐 멈추는
159
159
  // 정상 구간에서 소음이 출렁이지 않게 하기 위한 유예다.
160
- const noiseVolume = noisePlayer ? effectiveNoiseVolume(noisePlayer.settings) : 0;
160
+ const noiseVolume = noisePlayer ? noiseElementVolume(noisePlayer.settings) : 0;
161
161
  const noiseInUse = !!noisePlayer?.settings.enabled && noiseVolume > 0;
162
162
  useEffect(() => {
163
163
  // 소음이 이번 재생 세션에서 아예 꺼져 있으면(noiseInUse=false) noiseEl은 절대
@@ -220,7 +220,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
220
220
  const noiseEl = noiseAudioRef.current;
221
221
  if (!noiseEl)
222
222
  return;
223
- const volume = ns ? effectiveNoiseVolume(ns) : 0;
223
+ const volume = ns ? noiseElementVolume(ns) : 0;
224
224
  if (!ns || !ns.enabled || volume === 0) {
225
225
  fadeOutNoise();
226
226
  return;
@@ -8,7 +8,7 @@ type PlaybackState = 'stopped' | 'playing' | 'paused';
8
8
  export declare function useTTSPlayback({ ttsText, playbackRate, mainVolume, fillRef, waveProgressRef, setProgress, setPlaybackState, setStateLocal, fadeInNoise, fadeOutNoise, noiseSettingsRef, onDoneRef, handleEndRef, }: {
9
9
  ttsText?: string | string[] | null;
10
10
  playbackRate: number;
11
- /** 음성 볼륨 0–1 — 부르는 쪽이 문제 소리 볼륨(effectiveSpeechVolume)을 넘긴다 */
11
+ /** 음성 볼륨 0–1 — 문제 소리 = 전체 소리(effectiveSpeechVolume) */
12
12
  mainVolume: number;
13
13
  fillRef: RefObject<HTMLDivElement | null>;
14
14
  waveProgressRef: RefObject<HTMLDivElement | null>;
@@ -16,13 +16,13 @@ export type CoreNoiseSettings = {
16
16
  level: number;
17
17
  /**
18
18
  * 소음 비율 — 문제 소리 대비 소음의 배수, 0–2.0(0.1 칸). 1 = 소음과 문제 소리가 같은 크기.
19
- * 문제 소리 = mainVolume × min(1, 1/noiseRatio), 소음 = mainVolume × min(1, noiseRatio).
19
+ * 문제 소리 = mainVolume, 소음 들리는 크기 = mainVolume × noiseRatio(원본 소음 파일 기준).
20
20
  * 없으면 `level` 에서 옮긴다.
21
21
  */
22
22
  noiseRatio?: number;
23
23
  /** 소음 재생 속도(playbackRate). 0.75–1.25, 기본 1. */
24
24
  speed: number;
25
- /** 전체 소리 — 두 소리 중 큰 쪽의 크기. 0–1, 기본 1. 실제 두 볼륨은 `noiseRatio` 로 나뉜다. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
25
+ /** 전체 소리 = 문제 소리 볼륨. 0–1, 기본 1. 소음은 이 값 × `noiseRatio`. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
26
26
  mainVolume: number;
27
27
  /** 문장(메인) 재생 속도(playbackRate). 0.5–1.5, 기본 1. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
28
28
  mainSpeed: number;
@@ -58,6 +58,16 @@ export interface StorageResolvers {
58
58
  * @example 'https://cdn.example.com/sounds/noise'
59
59
  */
60
60
  noiseBaseUrl?: string;
61
+ /**
62
+ * (선택) `noiseBaseUrl` 의 소음 파일이 **원본 소음보다 몇 배 큰지**(진폭). 기본 1.
63
+ *
64
+ * 소음 비율 R 은 원본 파일 기준이라 `<audio>` 볼륨 = min(1, 전체 소리 × R ÷ 이 값) 이다.
65
+ * 볼륨은 1 을 넘을 수 없으므로 원본 파일로는 1배 넘는 소음이 1 에서 멈춘다 — 2배로 키운 파일과
66
+ * `noiseFileGain: 2` 를 함께 주면 0~2배가 정확히 들린다.
67
+ *
68
+ * @example 2
69
+ */
70
+ noiseFileGain?: number;
61
71
  }
62
72
  /**
63
73
  * Storage resolver를 런타임에 주입합니다.
@@ -100,3 +110,8 @@ export declare function downloadSound(filePath: string): Promise<Blob>;
100
110
  * @public
101
111
  */
102
112
  export declare function getNoiseBaseUrl(): string;
113
+ /**
114
+ * 소음 파일이 원본보다 몇 배 큰지 — configureStorage 의 noiseFileGain. 없거나 양수가 아니면 1.
115
+ * @public
116
+ */
117
+ export declare function getNoiseFileGain(): number;
@@ -116,3 +116,11 @@ export async function downloadSound(filePath) {
116
116
  export function getNoiseBaseUrl() {
117
117
  return getResolvers()?.noiseBaseUrl ?? '/sounds/noise';
118
118
  }
119
+ /**
120
+ * 소음 파일이 원본보다 몇 배 큰지 — configureStorage 의 noiseFileGain. 없거나 양수가 아니면 1.
121
+ * @public
122
+ */
123
+ export function getNoiseFileGain() {
124
+ const g = Number(getResolvers()?.noiseFileGain);
125
+ return Number.isFinite(g) && g > 0 ? g : 1;
126
+ }
@@ -1,6 +1,6 @@
1
1
  export { speak, stopSpeaking } from './utils/speak';
2
2
  export { getAssetBaseUrl, resolveAssetUrl, setAssetBaseUrl } from './utils/assets';
3
- export { configureStorage, downloadSound, fileExists, getSignedOrPublicUrl, resolveSoundPath, getNoiseBaseUrl } from './utils/storage';
3
+ export { configureStorage, downloadSound, fileExists, getSignedOrPublicUrl, resolveSoundPath, getNoiseBaseUrl, getNoiseFileGain } from './utils/storage';
4
4
  export type { StorageResolvers } from './utils/storage';
5
5
  export { configureTTS, getTTSUrlResolver, getTTSFallbackUrlResolver, clearAudioExistsCache } from './utils/ttsConfig';
6
6
  export { configurePronunciationResolver, getPronunciationResolver } from './utils/pronunciationConfig';
@@ -1,6 +1,6 @@
1
1
  export { speak, stopSpeaking } from './utils/speak';
2
2
  export { getAssetBaseUrl, resolveAssetUrl, setAssetBaseUrl } from './utils/assets';
3
- export { configureStorage, downloadSound, fileExists, getSignedOrPublicUrl, resolveSoundPath, getNoiseBaseUrl } from './utils/storage';
3
+ export { configureStorage, downloadSound, fileExists, getSignedOrPublicUrl, resolveSoundPath, getNoiseBaseUrl, getNoiseFileGain } from './utils/storage';
4
4
  export { configureTTS, getTTSUrlResolver, getTTSFallbackUrlResolver, clearAudioExistsCache } from './utils/ttsConfig';
5
5
  export { configurePronunciationResolver, getPronunciationResolver } from './utils/pronunciationConfig';
6
6
  export { getElementAnalyser, resumeAudioMeter, readLevel } from './utils/audioMeter';
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export declare const CORE_VERSION = "0.124.0";
49
+ export declare const CORE_VERSION = "0.125.0";
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export const CORE_VERSION = '0.124.0';
49
+ export const CORE_VERSION = '0.125.0';