@ihabdevteam/core 0.124.0 → 0.126.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/CHANGELOG.md +51 -0
- package/dist/components.cjs.js +177 -177
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +1 -1
- package/dist/ihabdevteam-core.cjs.js +181 -181
- package/dist/ihabdevteam-core.d.ts +55 -12
- package/dist/packages/core/src/CoreScope.d.ts +2 -0
- package/dist/packages/core/src/CoreScope.js +1 -0
- package/dist/packages/core/src/components/AudioMeter.js +13 -5
- package/dist/packages/core/src/components/Training/NoisePlayerContext.d.ts +14 -6
- package/dist/packages/core/src/components/Training/NoisePlayerContext.js +29 -33
- package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +17 -13
- package/dist/packages/core/src/components/Training/useNoiseFade.js +19 -15
- package/dist/packages/core/src/components/Training/useTTSPlayback.d.ts +1 -1
- package/dist/packages/core/src/types/training.d.ts +2 -2
- package/dist/packages/core/src/utils/audioMeter.d.ts +31 -9
- package/dist/packages/core/src/utils/audioMeter.js +135 -23
- package/dist/packages/core/src/utils/storage.d.ts +15 -0
- package/dist/packages/core/src/utils/storage.js +8 -0
- package/dist/packages/core/src/utils.d.ts +2 -2
- package/dist/packages/core/src/utils.js +2 -2
- package/dist/packages/core/src/version.d.ts +1 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/utils.cjs.js +2 -2
- package/package.json +1 -1
|
@@ -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.
|
|
1344
|
+
export declare const CORE_VERSION = "0.126.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
|
|
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
|
-
/** 전체 소리
|
|
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
|
/**
|
|
@@ -2221,11 +2223,15 @@ export declare function getDailySelection(): {
|
|
|
2221
2223
|
};
|
|
2222
2224
|
|
|
2223
2225
|
/**
|
|
2224
|
-
* 엘리먼트에 연결된 AnalyserNode를 반환(
|
|
2225
|
-
*
|
|
2226
|
+
* 엘리먼트에 연결된 AnalyserNode 를 반환(달리는 컨텍스트면 지금 태워서).
|
|
2227
|
+
* 컨텍스트가 suspended 면 **지금은 null** 을 주고 resume 뒤 태우기를 걸어 둔다 — 나중 값이 필요하면
|
|
2228
|
+
* `routeElementAudio` 를 기다린다. 실패 시 null(미지원/이미 다른 그래프에 소스됨 등).
|
|
2226
2229
|
*/
|
|
2227
2230
|
export declare function getElementAnalyser(el: HTMLMediaElement | null | undefined): AnalyserNode | null;
|
|
2228
2231
|
|
|
2232
|
+
/** @public 요소에 걸려고 한 볼륨(0~1) — 램프 출발점. 건 적이 없으면 요소 volume. */
|
|
2233
|
+
export declare function getElementVolume(el: HTMLMediaElement): number;
|
|
2234
|
+
|
|
2229
2235
|
/**
|
|
2230
2236
|
* 잡은 값에서 **사람이 읽을 수 있는 원인**을 꺼낸다.
|
|
2231
2237
|
*
|
|
@@ -2269,6 +2275,12 @@ export declare function getImageResolver(): ((word: string, homonum?: number) =>
|
|
|
2269
2275
|
*/
|
|
2270
2276
|
export declare function getNoiseBaseUrl(): string;
|
|
2271
2277
|
|
|
2278
|
+
/**
|
|
2279
|
+
* 소음 파일이 원본보다 몇 배 큰지 — configureStorage 의 noiseFileGain. 없거나 양수가 아니면 1.
|
|
2280
|
+
* @public
|
|
2281
|
+
*/
|
|
2282
|
+
export declare function getNoiseFileGain(): number;
|
|
2283
|
+
|
|
2272
2284
|
/** @public 현재 메모리에 적재된 인덱스 (없으면 localStorage 시도) */
|
|
2273
2285
|
export declare function getPairIndex(): PairIndex | null;
|
|
2274
2286
|
|
|
@@ -4646,14 +4658,20 @@ export declare function resolveTrainingPopupMeta(trainingData: Record<string, un
|
|
|
4646
4658
|
};
|
|
4647
4659
|
|
|
4648
4660
|
/**
|
|
4649
|
-
* 오디오 레벨
|
|
4661
|
+
* 오디오 레벨 미터 + 볼륨용 Web Audio 유틸.
|
|
4650
4662
|
*
|
|
4651
|
-
* HTMLMediaElement를
|
|
4652
|
-
* - createMediaElementSource는 엘리먼트당 1회만 가능 → WeakMap으로 캐싱.
|
|
4653
|
-
* -
|
|
4654
|
-
*
|
|
4655
|
-
*
|
|
4656
|
-
*
|
|
4663
|
+
* HTMLMediaElement 를 한 그래프에 태운다: `source → GainNode → (AnalyserNode, destination)`.
|
|
4664
|
+
* - createMediaElementSource 는 엘리먼트당 1회만 가능 → WeakMap 으로 캐싱.
|
|
4665
|
+
* - **볼륨은 GainNode 로 건다**(0.126.0). 실제 iPhone·iPad 에서는 `HTMLMediaElement.volume` 을
|
|
4666
|
+
* 스크립트로 바꿔도 소리에 안 먹는다(Apple 문서 — 늘 1). 그래프에 태운 요소는 volume 을 1 로
|
|
4667
|
+
* 두고 `gain` 으로 크기를 정하므로 기기와 상관없이 먹는다. 미터(analyser)는 gain 뒤라 들리는
|
|
4668
|
+
* 크기를 읽는다(요소 volume 이 소스 출력에 걸리던 전과 같은 결).
|
|
4669
|
+
* - **달리는(running) AudioContext 에만 태운다.** 태운 순간부터 요소 소리는 destination 연결로만
|
|
4670
|
+
* 나가므로, suspended(자동재생 정책) 컨텍스트에 태우면 resume 전까지 무음이다. 그래서 suspended
|
|
4671
|
+
* 이면 resume 이 끝난 뒤에 태우고, 그 전에는 요소가 제 볼륨으로 직접 울린다.
|
|
4672
|
+
* - Web Audio 가 없거나 태우기에 실패하면(미지원·이미 다른 그래프에 소스됨 등) 요소 volume 으로 물러선다.
|
|
4673
|
+
* - cross-origin 소스는 엘리먼트에 crossOrigin="anonymous" 가 있고 서버가 CORS 를 허용해야 그래프
|
|
4674
|
+
* 출력이 무음이 되지 않는다(코어의 문장·소음 요소는 둘 다 붙인다).
|
|
4657
4675
|
*/
|
|
4658
4676
|
/** 미터 AudioContext를 사용자 제스처 이후 resume(자동재생 정책 대응). */
|
|
4659
4677
|
export declare function resumeAudioMeter(): void;
|
|
@@ -4786,6 +4804,14 @@ export declare interface RingChartSegment {
|
|
|
4786
4804
|
/** 시맨틱 사이즈 → 지름(px)·링 두께(px) 프리셋 */
|
|
4787
4805
|
export declare type RingChartSize = 'small' | 'medium' | 'large';
|
|
4788
4806
|
|
|
4807
|
+
/**
|
|
4808
|
+
* 엘리먼트를 그래프에 태우고 AnalyserNode 를 준다(이미 태웠으면 그것).
|
|
4809
|
+
* 컨텍스트가 suspended 면 resume 을 기다린 뒤 태운다 — resume 이 안 되면(제스처 없음) null 이고
|
|
4810
|
+
* 요소는 제 볼륨으로 계속 울린다. Web Audio 미지원·실패도 null.
|
|
4811
|
+
* @public
|
|
4812
|
+
*/
|
|
4813
|
+
export declare function routeElementAudio(el: HTMLMediaElement | null | undefined): Promise<AnalyserNode | null>;
|
|
4814
|
+
|
|
4789
4815
|
/**
|
|
4790
4816
|
* @public 배열에서 무작위로 최대 n개를 뽑아 새 배열로 반환(원본 불변).
|
|
4791
4817
|
* n이 길이 이상이면 전체를 셔플해 반환한다.
|
|
@@ -5089,6 +5115,13 @@ export declare function setAssetBaseUrl(baseUrl?: string | null): void;
|
|
|
5089
5115
|
/** @public */
|
|
5090
5116
|
export declare function setCrosswordLogger(fn: CrosswordLogger | null): void;
|
|
5091
5117
|
|
|
5118
|
+
/**
|
|
5119
|
+
* @public 요소 볼륨(0~1)을 건다. 그래프에 태운 요소면 요소 volume 은 1 로 두고 GainNode 에
|
|
5120
|
+
* (짧은 시정수로 매끄럽게) 걸고 'gain', 아니면 요소 volume 에 걸고 'element'. 부르는 쪽이 매 프레임
|
|
5121
|
+
* 불러 램프를 만들어도 된다.
|
|
5122
|
+
*/
|
|
5123
|
+
export declare function setElementVolume(el: HTMLMediaElement, volume: number): 'gain' | 'element';
|
|
5124
|
+
|
|
5092
5125
|
/** @public */
|
|
5093
5126
|
export declare function setGameLeaderboardLoader(fn: LeaderboardFn): void;
|
|
5094
5127
|
|
|
@@ -5650,6 +5683,16 @@ export declare interface StorageResolvers {
|
|
|
5650
5683
|
* @example 'https://cdn.example.com/sounds/noise'
|
|
5651
5684
|
*/
|
|
5652
5685
|
noiseBaseUrl?: string;
|
|
5686
|
+
/**
|
|
5687
|
+
* (선택) `noiseBaseUrl` 의 소음 파일이 **원본 소음보다 몇 배 큰지**(진폭). 기본 1.
|
|
5688
|
+
*
|
|
5689
|
+
* 소음 비율 R 은 원본 파일 기준이라 `<audio>` 볼륨 = min(1, 전체 소리 × R ÷ 이 값) 이다.
|
|
5690
|
+
* 볼륨은 1 을 넘을 수 없으므로 원본 파일로는 1배 넘는 소음이 1 에서 멈춘다 — 2배로 키운 파일과
|
|
5691
|
+
* `noiseFileGain: 2` 를 함께 주면 0~2배가 정확히 들린다.
|
|
5692
|
+
*
|
|
5693
|
+
* @example 2
|
|
5694
|
+
*/
|
|
5695
|
+
noiseFileGain?: number;
|
|
5653
5696
|
}
|
|
5654
5697
|
|
|
5655
5698
|
/** @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);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cx } from '../utils/cx';
|
|
4
|
-
import { useEffect, useRef } from 'react';
|
|
5
|
-
import { getElementAnalyser, resumeAudioMeter, readLevel } from '../utils/audioMeter';
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { getElementAnalyser, resumeAudioMeter, readLevel, routeElementAudio } from '../utils/audioMeter';
|
|
6
6
|
import './AudioMeter.css';
|
|
7
7
|
// 레벨 표시 평활화(빠른 어택 / 느린 릴리스)
|
|
8
8
|
const RELEASE = 0.86;
|
|
@@ -27,14 +27,22 @@ export default function AudioMeter({ analyser, audioElement, orientation = 'hori
|
|
|
27
27
|
const fillRef = useRef(null);
|
|
28
28
|
const valueRef = useRef(null);
|
|
29
29
|
const rafRef = useRef(null);
|
|
30
|
+
/* 컨텍스트가 아직 suspended 면 getElementAnalyser 는 지금 null 을 준다(달리는 컨텍스트에만 태운다 —
|
|
31
|
+
audioMeter 머리말). resume 뒤 태워지면 그 노드로 다시 돈다. */
|
|
32
|
+
const [routed, setRouted] = useState(null);
|
|
30
33
|
useEffect(() => {
|
|
31
|
-
const node = analyser ?? getElementAnalyser(audioElement);
|
|
34
|
+
const node = analyser ?? getElementAnalyser(audioElement) ?? (routed && routed.el === audioElement ? routed.node : null);
|
|
32
35
|
const fill = fillRef.current;
|
|
33
36
|
if (!node || !fill) {
|
|
34
37
|
fill?.style.setProperty('--meter-level', '0');
|
|
35
38
|
if (valueRef.current)
|
|
36
39
|
valueRef.current.textContent = formatRelDb(floorDb, refDb, displayMin, displayMax);
|
|
37
|
-
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
if (!analyser && audioElement) {
|
|
42
|
+
void routeElementAudio(audioElement).then((n) => { if (!cancelled && n)
|
|
43
|
+
setRouted({ el: audioElement, node: n }); });
|
|
44
|
+
}
|
|
45
|
+
return () => { cancelled = true; };
|
|
38
46
|
}
|
|
39
47
|
resumeAudioMeter();
|
|
40
48
|
const buffer = new Float32Array(node.fftSize);
|
|
@@ -78,7 +86,7 @@ export default function AudioMeter({ analyser, audioElement, orientation = 'hori
|
|
|
78
86
|
cancelAnimationFrame(rafRef.current);
|
|
79
87
|
rafRef.current = null;
|
|
80
88
|
};
|
|
81
|
-
}, [analyser, audioElement, floorDb, refDb, displayMin, displayMax]);
|
|
89
|
+
}, [analyser, audioElement, routed, floorDb, refDb, displayMin, displayMax]);
|
|
82
90
|
const lengthCss = typeof length === 'number' ? `${length}px` : length;
|
|
83
91
|
const isVertical = orientation === 'vertical';
|
|
84
92
|
const trackStyle = lengthCss
|
|
@@ -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
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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
|
-
/** 전체 소리
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
*
|
|
46
|
-
*
|
|
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
|
-
|
|
61
|
-
return mainVolume * Math.min(1, noiseRatio);
|
|
56
|
+
return 전체소리(settings) * noiseRatioOf(settings);
|
|
62
57
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
*
|
|
76
|
-
*
|
|
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
|
-
|
|
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
|
}
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
|
|
6
6
|
// TrainingItem, TrainingGroup import 제거됨 (resolveMapping 제거로 불필요)
|
|
7
7
|
import { downloadSound, getSignedOrPublicUrl, resolveSoundPath } from '../../utils/storage';
|
|
8
8
|
import { getTTSUrlResolver } from '../../utils/ttsConfig';
|
|
9
|
-
import {
|
|
9
|
+
import { resumeAudioMeter, routeElementAudio, setElementVolume, getElementVolume } from '../../utils/audioMeter';
|
|
10
10
|
import { useNoisePlayer, effectiveNoiseVolume, effectiveSpeechVolume, noiseRatioOf } from './NoisePlayerContext';
|
|
11
11
|
import { useNoiseFade } from './useNoiseFade';
|
|
12
12
|
import { useTTSPlayback } from './useTTSPlayback';
|
|
@@ -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
|
-
//
|
|
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,
|
|
@@ -204,10 +204,14 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
204
204
|
setStateLocal("playMode");
|
|
205
205
|
set구간표시(true);
|
|
206
206
|
startTicker();
|
|
207
|
-
//
|
|
207
|
+
// 문장을 Web Audio 그래프(gain·미터)에 태운다 — 재생 제스처 시점에 resume, 컨텍스트가 달리고 나서야
|
|
208
|
+
// 태우므로 그 전엔 요소가 제 볼륨으로 직접 울린다(무음 방지). 태운 뒤 볼륨은 GainNode 로 걸린다.
|
|
208
209
|
try {
|
|
209
210
|
resumeAudioMeter();
|
|
210
|
-
|
|
211
|
+
void routeElementAudio(a).then((an) => { try {
|
|
212
|
+
registerAnalyserRef.current?.('main', an);
|
|
213
|
+
}
|
|
214
|
+
catch (e) { } });
|
|
211
215
|
}
|
|
212
216
|
catch (e) { }
|
|
213
217
|
// 소음 동기 시작
|
|
@@ -950,8 +954,8 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
950
954
|
}
|
|
951
955
|
catch (e) { }
|
|
952
956
|
}, [playbackRate]);
|
|
953
|
-
// 문장(메인) 볼륨 반영 — 문제 소리 볼륨(effectiveSpeechVolume).
|
|
954
|
-
//
|
|
957
|
+
// 문장(메인) 볼륨 반영 — 문제 소리 볼륨(effectiveSpeechVolume = 전체 소리). setElementVolume 이 그래프에 태운
|
|
958
|
+
// 요소면 GainNode, 아니면 요소 volume 에 건다(iOS 실기기는 요소 volume 이 안 먹는다). 재생 중이면 소음과 같은 150ms 램프로 옮기고, 멈춰 있으면 바로 건다.
|
|
955
959
|
// 백그라운드 탭에서는 RAF 가 멈춰 램프가 중간에 설 수 있으므로 끝값을 타이머로 한 번 더 박는다.
|
|
956
960
|
const speechRampRef = useRef({ raf: null, timer: null });
|
|
957
961
|
useEffect(() => {
|
|
@@ -971,10 +975,10 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
971
975
|
}
|
|
972
976
|
};
|
|
973
977
|
끊기();
|
|
974
|
-
const from = a
|
|
978
|
+
const from = getElementVolume(a);
|
|
975
979
|
if (a.paused || Math.abs(from - target) < 0.001 || typeof requestAnimationFrame !== 'function') {
|
|
976
980
|
try {
|
|
977
|
-
a
|
|
981
|
+
setElementVolume(a, target);
|
|
978
982
|
}
|
|
979
983
|
catch (e) { }
|
|
980
984
|
return;
|
|
@@ -983,14 +987,14 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
983
987
|
const step = () => {
|
|
984
988
|
const t = Math.min(1, (performance.now() - start) / 150);
|
|
985
989
|
try {
|
|
986
|
-
a
|
|
990
|
+
setElementVolume(a, from + (target - from) * t);
|
|
987
991
|
}
|
|
988
992
|
catch (e) { }
|
|
989
993
|
ramp.raf = t < 1 ? requestAnimationFrame(step) : null;
|
|
990
994
|
};
|
|
991
995
|
ramp.raf = requestAnimationFrame(step);
|
|
992
996
|
ramp.timer = setTimeout(() => { 끊기(); try {
|
|
993
|
-
a
|
|
997
|
+
setElementVolume(a, target);
|
|
994
998
|
}
|
|
995
999
|
catch (e) { } }, 200);
|
|
996
1000
|
return 끊기;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { noiseAssets } from '../../sounds/noiseAssets';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { resumeAudioMeter, routeElementAudio, setElementVolume, getElementVolume } from '../../utils/audioMeter';
|
|
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 =
|
|
27
|
+
// volume = 소음 <audio> 볼륨(noiseElementVolume — 전체 소리 × 소음 비율 ÷ 파일 gain, 상한 1)
|
|
28
28
|
const noiseSettingsRef = useRef(null);
|
|
29
29
|
const noiseFadeRafRef = useRef(null);
|
|
30
30
|
// 페이드 세대 토큰 — 하드스톱 타이머가 발화 시점에 "그 사이 새 페이드가 시작됐는지"를
|
|
@@ -47,24 +47,28 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
47
47
|
return;
|
|
48
48
|
noiseFadeTokenRef.current += 1;
|
|
49
49
|
cancelNoiseFade();
|
|
50
|
-
//
|
|
50
|
+
// 소음을 Web Audio 그래프(gain·미터)에 태운다 — 컨텍스트가 달리고 나서야 태우므로 그 전엔 요소가 직접 울린다.
|
|
51
|
+
// 볼륨은 setElementVolume 이 그래프면 GainNode, 아니면 요소 volume 에 건다(iOS 실기기는 요소 volume 이 안 먹는다).
|
|
51
52
|
try {
|
|
52
53
|
resumeAudioMeter();
|
|
53
|
-
|
|
54
|
+
void routeElementAudio(noiseEl).then((an) => { try {
|
|
55
|
+
registerAnalyserRef.current?.('noise', an);
|
|
56
|
+
}
|
|
57
|
+
catch (e) { } });
|
|
54
58
|
}
|
|
55
59
|
catch (e) { }
|
|
56
60
|
// 이미 재생 중이면(멀티소스 연달아 재생 — 단어1→단어2 전환마다 onPlay가 다시 호출됨)
|
|
57
61
|
// 볼륨을 0으로 리셋하지 않고 현재 볼륨에서 목표까지 부드럽게 이어간다 — 트랙 전환 때마다
|
|
58
62
|
// 소음이 뚝 끊겼다 다시 차오르던 문제 방지. 이미 목표 볼륨이면 램프가 사실상 no-op이다.
|
|
59
|
-
const startVol = noiseEl.paused ? 0 : noiseEl
|
|
63
|
+
const startVol = noiseEl.paused ? 0 : getElementVolume(noiseEl);
|
|
60
64
|
if (noiseEl.paused)
|
|
61
|
-
noiseEl
|
|
65
|
+
setElementVolume(noiseEl, 0);
|
|
62
66
|
noiseEl.play().catch(() => { });
|
|
63
67
|
const startTime = performance.now();
|
|
64
68
|
const step = () => {
|
|
65
69
|
const t = Math.min(1, (performance.now() - startTime) / durationMs);
|
|
66
70
|
try {
|
|
67
|
-
noiseEl
|
|
71
|
+
setElementVolume(noiseEl, startVol + (targetVolume - startVol) * t);
|
|
68
72
|
}
|
|
69
73
|
catch (e) { }
|
|
70
74
|
if (t < 1) {
|
|
@@ -85,11 +89,11 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
85
89
|
const token = ++noiseFadeTokenRef.current;
|
|
86
90
|
cancelNoiseFade();
|
|
87
91
|
const startTime = performance.now();
|
|
88
|
-
const startVol = noiseEl
|
|
92
|
+
const startVol = getElementVolume(noiseEl);
|
|
89
93
|
const step = () => {
|
|
90
94
|
const t = Math.min(1, (performance.now() - startTime) / durationMs);
|
|
91
95
|
try {
|
|
92
|
-
noiseEl
|
|
96
|
+
setElementVolume(noiseEl, startVol * (1 - t));
|
|
93
97
|
}
|
|
94
98
|
catch (e) { }
|
|
95
99
|
if (t < 1) {
|
|
@@ -123,7 +127,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
123
127
|
}
|
|
124
128
|
try {
|
|
125
129
|
if (!noiseEl.paused) {
|
|
126
|
-
noiseEl
|
|
130
|
+
setElementVolume(noiseEl, 0);
|
|
127
131
|
noiseEl.pause();
|
|
128
132
|
onComplete?.();
|
|
129
133
|
}
|
|
@@ -157,7 +161,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
157
161
|
// 개별 레이스를 전부 틀어막는 대신, 잘못된 상태를 주기적으로 감지해 회수한다.
|
|
158
162
|
// 두 번 연속(약 0.8초) 확인 후에 정지 — 멀티소스 트랙 전환처럼 메인이 잠깐 멈추는
|
|
159
163
|
// 정상 구간에서 소음이 출렁이지 않게 하기 위한 유예다.
|
|
160
|
-
const noiseVolume = noisePlayer ?
|
|
164
|
+
const noiseVolume = noisePlayer ? noiseElementVolume(noisePlayer.settings) : 0;
|
|
161
165
|
const noiseInUse = !!noisePlayer?.settings.enabled && noiseVolume > 0;
|
|
162
166
|
useEffect(() => {
|
|
163
167
|
// 소음이 이번 재생 세션에서 아예 꺼져 있으면(noiseInUse=false) noiseEl은 절대
|
|
@@ -220,7 +224,7 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
220
224
|
const noiseEl = noiseAudioRef.current;
|
|
221
225
|
if (!noiseEl)
|
|
222
226
|
return;
|
|
223
|
-
const volume = ns ?
|
|
227
|
+
const volume = ns ? noiseElementVolume(ns) : 0;
|
|
224
228
|
if (!ns || !ns.enabled || volume === 0) {
|
|
225
229
|
fadeOutNoise();
|
|
226
230
|
return;
|
|
@@ -267,12 +271,12 @@ export function useNoiseFade({ noisePlayer, audioRef, playbackStateRef, }) {
|
|
|
267
271
|
else {
|
|
268
272
|
// 이미 재생 중 — 볼륨만 부드럽게 조절
|
|
269
273
|
cancelNoiseFade();
|
|
270
|
-
const startVol = noiseEl
|
|
274
|
+
const startVol = getElementVolume(noiseEl);
|
|
271
275
|
const startTime = performance.now();
|
|
272
276
|
const ramp = () => {
|
|
273
277
|
const t = Math.min(1, (performance.now() - startTime) / 150);
|
|
274
278
|
try {
|
|
275
|
-
noiseEl
|
|
279
|
+
setElementVolume(noiseEl, startVol + (volume - startVol) * t);
|
|
276
280
|
}
|
|
277
281
|
catch (e) { }
|
|
278
282
|
if (t < 1)
|
|
@@ -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 —
|
|
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
|
|
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
|
-
/** 전체 소리
|
|
25
|
+
/** 전체 소리 = 문제 소리 볼륨. 0–1, 기본 1. 소음은 이 값 × `noiseRatio`. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
|
|
26
26
|
mainVolume: number;
|
|
27
27
|
/** 문장(메인) 재생 속도(playbackRate). 0.5–1.5, 기본 1. 계정 설정("훈련" 패널)과 훈련 화면 툴팁이 함께 영속화한다. */
|
|
28
28
|
mainSpeed: number;
|
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 오디오 레벨
|
|
2
|
+
* 오디오 레벨 미터 + 볼륨용 Web Audio 유틸.
|
|
3
3
|
*
|
|
4
|
-
* HTMLMediaElement를
|
|
5
|
-
* - createMediaElementSource는 엘리먼트당 1회만 가능 → WeakMap으로 캐싱.
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* HTMLMediaElement 를 한 그래프에 태운다: `source → GainNode → (AnalyserNode, destination)`.
|
|
5
|
+
* - createMediaElementSource 는 엘리먼트당 1회만 가능 → WeakMap 으로 캐싱.
|
|
6
|
+
* - **볼륨은 GainNode 로 건다**(0.126.0). 실제 iPhone·iPad 에서는 `HTMLMediaElement.volume` 을
|
|
7
|
+
* 스크립트로 바꿔도 소리에 안 먹는다(Apple 문서 — 늘 1). 그래프에 태운 요소는 volume 을 1 로
|
|
8
|
+
* 두고 `gain` 으로 크기를 정하므로 기기와 상관없이 먹는다. 미터(analyser)는 gain 뒤라 들리는
|
|
9
|
+
* 크기를 읽는다(요소 volume 이 소스 출력에 걸리던 전과 같은 결).
|
|
10
|
+
* - **달리는(running) AudioContext 에만 태운다.** 태운 순간부터 요소 소리는 destination 연결로만
|
|
11
|
+
* 나가므로, suspended(자동재생 정책) 컨텍스트에 태우면 resume 전까지 무음이다. 그래서 suspended
|
|
12
|
+
* 이면 resume 이 끝난 뒤에 태우고, 그 전에는 요소가 제 볼륨으로 직접 울린다.
|
|
13
|
+
* - Web Audio 가 없거나 태우기에 실패하면(미지원·이미 다른 그래프에 소스됨 등) 요소 volume 으로 물러선다.
|
|
14
|
+
* - cross-origin 소스는 엘리먼트에 crossOrigin="anonymous" 가 있고 서버가 CORS 를 허용해야 그래프
|
|
15
|
+
* 출력이 무음이 되지 않는다(코어의 문장·소음 요소는 둘 다 붙인다).
|
|
10
16
|
*/
|
|
11
17
|
/** 미터 AudioContext를 사용자 제스처 이후 resume(자동재생 정책 대응). */
|
|
12
18
|
export declare function resumeAudioMeter(): void;
|
|
13
19
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
20
|
+
* 엘리먼트를 그래프에 태우고 AnalyserNode 를 준다(이미 태웠으면 그것).
|
|
21
|
+
* 컨텍스트가 suspended 면 resume 을 기다린 뒤 태운다 — resume 이 안 되면(제스처 없음) null 이고
|
|
22
|
+
* 요소는 제 볼륨으로 계속 울린다. Web Audio 미지원·실패도 null.
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export declare function routeElementAudio(el: HTMLMediaElement | null | undefined): Promise<AnalyserNode | null>;
|
|
26
|
+
/**
|
|
27
|
+
* 엘리먼트에 연결된 AnalyserNode 를 반환(달리는 컨텍스트면 지금 태워서).
|
|
28
|
+
* 컨텍스트가 suspended 면 **지금은 null** 을 주고 resume 뒤 태우기를 걸어 둔다 — 나중 값이 필요하면
|
|
29
|
+
* `routeElementAudio` 를 기다린다. 실패 시 null(미지원/이미 다른 그래프에 소스됨 등).
|
|
16
30
|
*/
|
|
17
31
|
export declare function getElementAnalyser(el: HTMLMediaElement | null | undefined): AnalyserNode | null;
|
|
32
|
+
/**
|
|
33
|
+
* @public 요소 볼륨(0~1)을 건다. 그래프에 태운 요소면 요소 volume 은 1 로 두고 GainNode 에
|
|
34
|
+
* (짧은 시정수로 매끄럽게) 걸고 'gain', 아니면 요소 volume 에 걸고 'element'. 부르는 쪽이 매 프레임
|
|
35
|
+
* 불러 램프를 만들어도 된다.
|
|
36
|
+
*/
|
|
37
|
+
export declare function setElementVolume(el: HTMLMediaElement, volume: number): 'gain' | 'element';
|
|
38
|
+
/** @public 요소에 걸려고 한 볼륨(0~1) — 램프 출발점. 건 적이 없으면 요소 volume. */
|
|
39
|
+
export declare function getElementVolume(el: HTMLMediaElement): number;
|
|
18
40
|
/** AnalyserNode의 현재 레벨(RMS, 0~1). */
|
|
19
41
|
export declare function readLevel(analyser: AnalyserNode, buffer: Float32Array<ArrayBuffer>): number;
|