primocode 10.10.1 → 10.11.1
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/README.md +10 -2
- package/lib/catalogo.js +1 -0
- package/lib/estudio/cena.js +6 -0
- package/lib/estudio/efeitos-fabrica/brilho-varredura.jsx +25 -0
- package/lib/estudio/efeitos-fabrica/cor-inunda.jsx +30 -0
- package/lib/estudio/efeitos-fabrica/mola-com-peso.jsx +28 -0
- package/lib/estudio/efeitos-fabrica/peca-impacto-3d.jsx +37 -0
- package/lib/estudio/efeitos-fabrica/raios-de-luz.jsx +29 -0
- package/lib/estudio/efeitos-fabrica/risco-multilinha.jsx +30 -0
- package/lib/estudio/efeitos.js +21 -4
- package/lib/estudio/index.js +45 -3
- package/lib/estudio/kit/gerativo.jsx +16 -0
- package/lib/estudio/kit/mundo.jsx +11 -5
- package/lib/estudio/kit/natural.js +161 -0
- package/lib/estudio/kit/personagem.jsx +36 -20
- package/lib/estudio/kit/voxel.jsx +129 -64
- package/lib/estudio/kit.js +1 -1
- package/lib/estudio/revisor.js +5 -2
- package/lib/estudio/sem-narracao.js +189 -0
- package/lib/estudio/sons-leves.js +15 -0
- package/package.json +2 -2
- package/skills/motion-de-primeira/SKILL.md +4 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/* kit/natural.js — o movimento com PESO: as curvas que tiram o 3D do robótico.
|
|
2
|
+
*
|
|
3
|
+
* "a animação do boneco, a movimentação, é muito robótico" (Joel, 14/10)
|
|
4
|
+
*
|
|
5
|
+
* O boneco mexia em seno puro: todo membro no mesmo tempo, a mesma volta
|
|
6
|
+
* idêntica para sempre, parada seca quando a ação acabava e o corpo congelado
|
|
7
|
+
* quando não falava. Aqui moram os princípios de animação, em funções puras
|
|
8
|
+
* (o node testa, o kit/voxel.jsx e o kit/personagem.jsx desenham):
|
|
9
|
+
* - mola: massa e amortecimento — acelera, passa um pouco do ponto e assenta;
|
|
10
|
+
* - antecipa: recua antes de ir e passa um tico depois (follow-through);
|
|
11
|
+
* - atraso: tronco, cabeça, braço e mão com tempos diferentes (sobreposição);
|
|
12
|
+
* - ruido: a micro variação que nunca repete, igual em todo render (semente);
|
|
13
|
+
* - respiracao: inspira mais rápido do que expira, o ritmo varia um pouco;
|
|
14
|
+
* - salto: agacha, estica no impulso, cai acelerando, amassa no pouso e assenta;
|
|
15
|
+
* - passo: o quadril que balança, o corpo que desce no contato, o tronco que
|
|
16
|
+
* gira contra o quadril e o braço que vai atrasado da perna.
|
|
17
|
+
* Nada de Math.random nem Date.now: o mesmo quadro sai igual sempre. */
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
21
|
+
const suave = (x) => { const k = clamp(x); return k * k * (3 - 2 * k); };
|
|
22
|
+
|
|
23
|
+
/** Um número de 0 a 1 a partir de um número inteiro e de uma semente (texto ou número). */
|
|
24
|
+
function sementeDe(s) {
|
|
25
|
+
if (typeof s === 'number') return s;
|
|
26
|
+
let h = 2166136261;
|
|
27
|
+
for (const c of String(s || '')) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); }
|
|
28
|
+
return (h >>> 0) % 100000;
|
|
29
|
+
}
|
|
30
|
+
function sorte(n, semente = 0) {
|
|
31
|
+
const x = Math.sin(n * 127.1 + sementeDe(semente) * 311.7) * 43758.5453;
|
|
32
|
+
return x - Math.floor(x);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Ruído suave de -1 a 1 (dois tons somados): a deriva que nunca repete igual. */
|
|
36
|
+
function ruido(t, semente = 0) {
|
|
37
|
+
const tom = (x, s) => {
|
|
38
|
+
const i = Math.floor(x), f = x - i;
|
|
39
|
+
const a = sorte(i, s) * 2 - 1, b = sorte(i + 1, s) * 2 - 1;
|
|
40
|
+
const k = f * f * f * (f * (f * 6 - 15) + 10); // o quíntico: sem quina na emenda
|
|
41
|
+
return a + (b - a) * k;
|
|
42
|
+
};
|
|
43
|
+
const s = sementeDe(semente);
|
|
44
|
+
return tom(t, s) * 0.68 + tom(t * 2.17 + 13.3, s + 7) * 0.32;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A MOLA: a resposta de massa-mola-amortecedor indo de 0 a 1, `s` segundos
|
|
49
|
+
* depois do gatilho. `freq` em Hz, `amort` < 1 passa do ponto e volta.
|
|
50
|
+
*/
|
|
51
|
+
function mola(s, { freq = 2.2, amort = 0.5 } = {}) {
|
|
52
|
+
if (s <= 0) return 0;
|
|
53
|
+
const w = 2 * Math.PI * freq;
|
|
54
|
+
if (amort >= 1) return 1 - Math.exp(-w * s) * (1 + w * s);
|
|
55
|
+
const wd = w * Math.sqrt(1 - amort * amort);
|
|
56
|
+
return 1 - Math.exp(-amort * w * s) * (Math.cos(wd * s) + (amort * w / wd) * Math.sin(wd * s));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A curva de 0 a 1 que RECUA antes de ir (antecipação) e passa um tico do fim
|
|
61
|
+
* antes de assentar (follow-through). Lenta nas pontas, rápida no meio.
|
|
62
|
+
*/
|
|
63
|
+
function antecipa(x, { recuo = 0.1, passa = 0.06 } = {}) {
|
|
64
|
+
const k = clamp(x);
|
|
65
|
+
const base = suave(k);
|
|
66
|
+
const antes = -recuo * Math.sin(Math.PI * clamp(k / 0.3)) * (k < 0.3 ? 1 : 0);
|
|
67
|
+
const depois = passa * Math.sin(Math.PI * clamp((k - 0.62) / 0.38)) * (k > 0.62 ? 1 : 0);
|
|
68
|
+
return base + antes + depois;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* O PESO DE UMA AÇÃO entre `de` e `ate` (segundos): entra com mola (passa e
|
|
73
|
+
* assenta), sai devagar, e nunca corta seco. `atraso` é a sobreposição: o
|
|
74
|
+
* membro que vem depois (cotovelo depois do ombro, cabeça depois do tronco).
|
|
75
|
+
*/
|
|
76
|
+
function envelope(t, de, ate, { atraso = 0, entra = 2.4, sai = 0.55, amort = 0.55 } = {}) {
|
|
77
|
+
const tt = t - atraso;
|
|
78
|
+
if (tt < de) return 0;
|
|
79
|
+
const dentro = mola(tt - de, { freq: entra, amort }); // passa de 1 e volta: o membro assenta
|
|
80
|
+
const fora = tt > ate ? 1 - suave((tt - ate) / sai) : 1;
|
|
81
|
+
return Math.max(0, dentro * fora);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* O GESTO de ênfase, `s` segundos depois de começar: o braço cai um pouco
|
|
86
|
+
* (antecipação), sobe rápido passando do ponto, segura e desce devagar.
|
|
87
|
+
* Dura ~1,1 s; fora disso é 0.
|
|
88
|
+
*/
|
|
89
|
+
function golpe(s) {
|
|
90
|
+
if (s <= 0 || s >= 1.15) return 0;
|
|
91
|
+
const sobe = s < 0.1 ? -0.14 * Math.sin(Math.PI * s / 0.1) : mola(s - 0.1, { freq: 2.6, amort: 0.5 });
|
|
92
|
+
const desce = 1 - suave((s - 0.55) / 0.6);
|
|
93
|
+
return sobe * desce;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A RESPIRAÇÃO de 0 a 1: inspira em ~40% do ciclo, expira no resto, e o
|
|
98
|
+
* ciclo (≈3,4 s) varia um pouco a cada volta, pela semente.
|
|
99
|
+
*/
|
|
100
|
+
function respiracao(t, semente = 0) {
|
|
101
|
+
const periodo = 3.4;
|
|
102
|
+
const fase = t / periodo + 0.12 * ruido(t * 0.15, semente) + sorte(1, semente);
|
|
103
|
+
const p = fase - Math.floor(fase);
|
|
104
|
+
return p < 0.4 ? suave(p / 0.4) : 1 - suave((p - 0.4) / 0.6);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* O SALTO, num ciclo de `periodo` s: agacha (amassa), impulsiona (estica),
|
|
109
|
+
* sobe e cai em parábola (a gravidade: a queda acelera), amassa no pouso e
|
|
110
|
+
* assenta. Devolve {y, sx, sy}: a altura e a escala de amassar/esticar
|
|
111
|
+
* (o volume fica: sx ≈ 1/√sy).
|
|
112
|
+
*/
|
|
113
|
+
function salto(t, { periodo = 0.95, altura = 0.32, fase = 0 } = {}) {
|
|
114
|
+
const p = ((t / periodo + fase) % 1 + 1) % 1;
|
|
115
|
+
const AGACHA = 0.16, AR = 0.6, POUSO = 1 - AGACHA - AR;
|
|
116
|
+
let y = 0, sy = 1;
|
|
117
|
+
if (p < AGACHA) {
|
|
118
|
+
const k = p / AGACHA;
|
|
119
|
+
const amassa = Math.sin(Math.PI * Math.min(1, k * 1.25)) * (k < 0.8 ? 1 : 0);
|
|
120
|
+
sy = 1 - 0.14 * amassa;
|
|
121
|
+
y = -0.05 * altura * amassa;
|
|
122
|
+
if (k >= 0.8) sy = 1 + 0.12 * suave((k - 0.8) / 0.2); // o impulso: estica antes de sair do chão
|
|
123
|
+
} else if (p < AGACHA + AR) {
|
|
124
|
+
const q = (p - AGACHA) / AR;
|
|
125
|
+
y = altura * 4 * q * (1 - q); // a parábola: sobe freando, desce acelerando
|
|
126
|
+
sy = 1 + 0.12 * Math.pow(Math.abs(1 - 2 * q), 1.5); // estica com a velocidade, redondo no alto
|
|
127
|
+
} else {
|
|
128
|
+
const k = (p - AGACHA - AR) / POUSO;
|
|
129
|
+
const amortece = Math.exp(-4.2 * k) * Math.cos(k * Math.PI * 2.2);
|
|
130
|
+
sy = 1 - 0.18 * amortece; // o pouso amassa e assenta em mola
|
|
131
|
+
y = -0.04 * altura * Math.max(0, amortece);
|
|
132
|
+
}
|
|
133
|
+
return { y, sy, sx: 1 / Math.sqrt(Math.max(0.5, sy)) };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* O PASSO, `t` em s, `ritmo` em passos por segundo (andar ≈ 1,8, correr ≈ 3).
|
|
138
|
+
* Devolve os ângulos (rad) e o deslocamento do corpo:
|
|
139
|
+
* perna[esq,dir] balanço da coxa (para frente é negativo no eixo X)
|
|
140
|
+
* braco[esq,dir] o braço oposto à perna, atrasado (sobreposição)
|
|
141
|
+
* quadril o rolar do quadril para o lado da perna que apoia
|
|
142
|
+
* giro o tronco que gira contra o quadril
|
|
143
|
+
* sobe o corpo desce no contato e sobe na passagem
|
|
144
|
+
*/
|
|
145
|
+
function passo(t, { ritmo = 1.8, amplitude = 0.42, correr = false, semente = 0 } = {}) {
|
|
146
|
+
const f = Math.PI * ritmo * t + sorte(3, semente) * 0.3;
|
|
147
|
+
// A perna vai para frente mais rápido do que volta no apoio: a onda torta.
|
|
148
|
+
const onda = (x) => Math.sin(x + 0.22 * Math.sin(x));
|
|
149
|
+
const a = amplitude * (correr ? 1.6 : 1);
|
|
150
|
+
const atrasoBraco = correr ? 0.18 : 0.32; // o braço chega depois da perna
|
|
151
|
+
return {
|
|
152
|
+
perna: [onda(f) * a, onda(f + Math.PI) * a],
|
|
153
|
+
braco: [onda(f + Math.PI - atrasoBraco) * a * 0.9, onda(f - atrasoBraco) * a * 0.9],
|
|
154
|
+
quadril: Math.sin(f - 0.35) * (correr ? 0.03 : 0.055),
|
|
155
|
+
giro: -Math.sin(f - 0.15) * (correr ? 0.12 : 0.07),
|
|
156
|
+
sobe: (0.5 + 0.5 * Math.cos(2 * f)) * (correr ? 0.09 : 0.045),
|
|
157
|
+
inclina: correr ? 0.14 : 0.03, // quem corre vai com o tronco para frente
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { clamp, suave, sorte, ruido, mola, antecipa, envelope, golpe, respiracao, salto, passo };
|
|
@@ -16,6 +16,8 @@ import { ThreeCanvas } from '@remotion/three';
|
|
|
16
16
|
import * as THREE from 'three';
|
|
17
17
|
import { useThree } from '@react-three/fiber';
|
|
18
18
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
19
|
+
// O movimento com peso (mola, antecipação, sobreposição, respiração): kit/natural.js.
|
|
20
|
+
import { ruido, envelope, golpe, respiracao } from './natural';
|
|
19
21
|
|
|
20
22
|
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
21
23
|
const lerp = (a, b, k) => a + (b - a) * k;
|
|
@@ -93,10 +95,10 @@ function gesto(falas, quem, t) {
|
|
|
93
95
|
if (f.quem !== quem || t < f.de - 0.5 || t > f.ate + 1) continue;
|
|
94
96
|
f.palavras.forEach((w, k) => {
|
|
95
97
|
if (String(w.t).replace(/[^\p{L}\d]/gu, '').length < 6 && !/\d/.test(w.t)) return;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (k % 2) esq = Math.
|
|
98
|
+
// Recua, sobe passando do ponto e desce devagar (natural.golpe), não um seno.
|
|
99
|
+
const b = golpe(t - (w.de - 0.15));
|
|
100
|
+
if (b === 0) return;
|
|
101
|
+
if (k % 2) esq = Math.abs(b) > Math.abs(esq) ? b : esq; else dir = Math.abs(b) > Math.abs(dir) ? b : dir;
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
return [esq, dir];
|
|
@@ -256,12 +258,14 @@ function RostoRobo({ p, est, acento }) {
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
/* ── O CORPO, OS BRAÇOS E O PERSONAGEM INTEIRO ───────────────────────────── */
|
|
259
|
-
function Braco({ lado, g, cor, pele, robo, t }) {
|
|
260
|
-
// Repouso: o antebraço deitado na mesa
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
261
|
+
function Braco({ lado, g, gc = g, gp = gc, cor, pele, robo, t, sem = 0 }) {
|
|
262
|
+
// Repouso: o antebraço deitado na mesa, que deriva de leve (nunca congelado).
|
|
263
|
+
// Gesto: o ombro leva, o cotovelo (gc) e o pulso (gp) vêm depois — a sobreposição.
|
|
264
|
+
const deriva = ruido(t * 0.4, sem + lado * 5) * 0.03;
|
|
265
|
+
const ombroX = lerp(-0.3, -0.62, g) + Math.sin(t * 6 + lado) * 0.05 * Math.max(0, g) + deriva;
|
|
266
|
+
const ombroZ = lado * lerp(0.08, 0.28, Math.max(0, g));
|
|
267
|
+
const cotovelo = lerp(-1.17, -1.75, gc) + Math.sin(t * 7.3 + lado * 2 - 0.8) * 0.12 * Math.max(0, gc) + deriva * 0.5;
|
|
268
|
+
const pulso = Math.sin(t * 9 + lado - 1.4) * 0.25 * Math.max(0, gp) + ruido(t * 0.7, sem + lado) * 0.06;
|
|
265
269
|
const tecido = robo ? <Mat c={cor} r={0.3} m={0.1} cc={1} /> : <Mat c={cor} r={0.85} sheen="#ffffff" />;
|
|
266
270
|
return (
|
|
267
271
|
<group position={[lado * 0.44, 1.18, 0]} rotation={[ombroX, 0, ombroZ]}>
|
|
@@ -279,7 +283,8 @@ function Braco({ lado, g, cor, pele, robo, t }) {
|
|
|
279
283
|
function Personagem({ p, est, acento, pos, giro }) {
|
|
280
284
|
const robo = p.tipo === 'robo';
|
|
281
285
|
const corpo = robo ? p.cor : p.roupa;
|
|
282
|
-
|
|
286
|
+
// O fôlego: inspira rápido, expira devagar, o ritmo varia (natural.respiracao).
|
|
287
|
+
const resp = 1 + respiracao(est.t, (est.semente || 0) + pos[0]) * 0.016;
|
|
283
288
|
const [cy, cp, cr] = est.cabeca;
|
|
284
289
|
return (
|
|
285
290
|
<group position={pos} rotation={[0, giro, 0]}>
|
|
@@ -293,8 +298,7 @@ function Personagem({ p, est, acento, pos, giro }) {
|
|
|
293
298
|
: <mesh position={[0, 1.27, 0]} rotation={[Math.PI / 2, 0, 0]}><torusGeometry args={[0.15, 0.04, 12, 32]} /><Mat c={new THREE.Color(corpo).multiplyScalar(0.7).getStyle()} r={0.85} /></mesh>}
|
|
294
299
|
<mesh position={[0, 1.3, 0]}><cylinderGeometry args={[0.12, 0.13, 0.25, 24]} />{robo ? <Mat c="#3a3e48" r={0.4} m={0.6} /> : <Mat c={p.pele} r={0.6} sheen="#ff9a8a" />}</mesh>
|
|
295
300
|
</group>
|
|
296
|
-
<Braco lado={
|
|
297
|
-
<Braco lado={1} g={est.gesto[1]} cor={corpo} pele={p.pele} robo={robo} t={est.t} />
|
|
301
|
+
{[-1, 1].map((l) => { const k = l > 0 ? 1 : 0; return <Braco key={l} lado={l} g={est.gesto[k]} gc={(est.gestoAtras || est.gesto)[k]} gp={(est.gestoMao || est.gesto)[k]} cor={corpo} pele={p.pele} robo={robo} t={est.t} sem={est.semente || 0} />; })}
|
|
298
302
|
<group position={[0, 1.62, 0]} rotation={[cp, cy, cr]} castShadow>
|
|
299
303
|
{robo ? <RostoRobo p={p} est={est} acento={acento} /> : <Rosto p={p} est={est} />}
|
|
300
304
|
</group>
|
|
@@ -529,19 +533,31 @@ export function PersonagemCena({ el, t }) {
|
|
|
529
533
|
for (const f of falas) if (f.quem === i && f.de <= t + 0.2) e = t <= f.ate + 2.5 ? f.emocao : 'neutro';
|
|
530
534
|
return e;
|
|
531
535
|
};
|
|
536
|
+
// O quanto cada um está falando (0 a 1, sem degrau): os acenos trocam com mola.
|
|
537
|
+
const pesoDaFala = (i, reacao = 0) => falas.reduce((m, f) => (f.quem === i ? Math.max(m, Math.min(1, envelope(t, f.de + reacao, f.ate, { entra: 2.6, sai: 0.45, amort: 0.7 }))) : m), 0);
|
|
532
538
|
const estado = (i) => {
|
|
539
|
+
const sem = i * 13 + 1;
|
|
533
540
|
const fala = falandoAgora(i);
|
|
534
|
-
const
|
|
541
|
+
const wFala = pesoDaFala(i);
|
|
542
|
+
// Quem ouve reage ~0,2 s depois, com mola, e volta devagar.
|
|
543
|
+
const wOuve = dois ? pesoDaFala(1 - i, 0.2) : 0;
|
|
535
544
|
const enfase = gesto(falas, i, t);
|
|
536
|
-
const nod =
|
|
545
|
+
const nod = lerp(ruido(t * 0.5, sem + 3) * 0.015, Math.sin(t * 5.2 + i) * 0.028 + ruido(t * 2.4, sem + 2) * 0.016, wFala)
|
|
546
|
+
+ wOuve * Math.max(0, Math.sin(t * 2.1 + i)) * 0.04;
|
|
537
547
|
const emo = emocaoDe(i);
|
|
538
|
-
|
|
539
|
-
|
|
548
|
+
// A inclinação da emoção chega e vai embora com peso, não troca no quadro.
|
|
549
|
+
let inclina = 0;
|
|
550
|
+
for (const f of falas) {
|
|
551
|
+
if (f.quem !== i || f.de > t + 0.2) continue;
|
|
552
|
+
inclina = ((EMO[f.emocao] || EMO.neutro).inclina || 0) * Math.min(1.05, envelope(t, f.de - 0.2, f.ate + 2.5, { entra: 1.4, sai: 0.8, amort: 0.6 }));
|
|
553
|
+
}
|
|
554
|
+
const micro = (hash(Math.floor(t * 1.5 + hash(i) * 0.9) + i * 7) - 0.5) * 0.08;
|
|
540
555
|
return {
|
|
541
|
-
t, falando: fala, emocao: emo, boca: boca(falas, i, t), pisca: piscada(i, t),
|
|
542
|
-
gesto: enfase, enfase: Math.max(enfase[0], enfase[1]),
|
|
556
|
+
t, semente: sem, falando: fala, emocao: emo, boca: boca(falas, i, t), pisca: piscada(i, t),
|
|
557
|
+
gesto: enfase, gestoAtras: gesto(falas, i, t - 0.08), gestoMao: gesto(falas, i, t - 0.15), enfase: Math.max(enfase[0], enfase[1], 0),
|
|
543
558
|
olhar: dois ? [micro, 0.02] : [micro * 0.5, 0.04],
|
|
544
|
-
|
|
559
|
+
// A cabeça deriva pelo ruído (nunca a mesma volta) e vem ~0,06 s atrás do corpo.
|
|
560
|
+
cabeca: [ruido((t - 0.06) * 0.35, sem + 5) * 0.09 + wOuve * 0.05, nod + (1 - wFala) * 0.03, inclina * (i ? -1 : 1) + ruido((t - 0.06) * 0.25, sem + 7) * 0.025],
|
|
545
561
|
};
|
|
546
562
|
};
|
|
547
563
|
const posDe = (i) => (dois ? [(elenco[i].lado || 0) * 0.95, 0, 0] : [0, 0, 0]);
|
|
@@ -31,6 +31,8 @@ import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
|
|
|
31
31
|
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
32
32
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
33
33
|
import { Mundo, Animal, Coisa, alturaDoBicho, frenteDoBicho, useIcones } from './mundo.jsx';
|
|
34
|
+
// O movimento com peso (mola, antecipação, sobreposição, respiração, salto, passo): kit/natural.js.
|
|
35
|
+
import { ruido, envelope, golpe, respiracao, salto, passo } from './natural';
|
|
34
36
|
|
|
35
37
|
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
36
38
|
const lerp = (a, b, k) => a + (b - a) * k;
|
|
@@ -144,10 +146,10 @@ function gesto(falas, quem, t) {
|
|
|
144
146
|
if (f.quem !== quem || t < f.de - 0.5 || t > f.ate + 1) continue;
|
|
145
147
|
f.palavras.forEach((w, k) => {
|
|
146
148
|
if (String(w.t).replace(/[^\p{L}\d]/gu, '').length < 6 && !/\d/.test(w.t)) return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (k % 2) esq = Math.
|
|
149
|
+
// O braço recua, sobe passando do ponto e desce devagar (natural.golpe), não um seno.
|
|
150
|
+
const b = golpe(t - (w.de - 0.15));
|
|
151
|
+
if (b === 0) return;
|
|
152
|
+
if (k % 2) esq = Math.abs(b) > Math.abs(esq) ? b : esq; else dir = Math.abs(b) > Math.abs(dir) ? b : dir;
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
155
|
return [esq, dir];
|
|
@@ -372,26 +374,36 @@ function CabecaFacetada({ p, est }) {
|
|
|
372
374
|
/* A AÇÃO da fala mexe o braço por cima do gesto: acenar (a mão direita no
|
|
373
375
|
alto, abanando), apontar (o braço esticado para a frente), comemorar (os
|
|
374
376
|
dois para cima), pensar (a mão no queixo), dançar (os dois balançando). */
|
|
375
|
-
function poseDoBraco(acao, lado, t, k) {
|
|
377
|
+
function poseDoBraco(acao, lado, t, k, sem = 0) {
|
|
376
378
|
if (!acao || k <= 0) return null;
|
|
377
379
|
const dir = lado > 0;
|
|
378
|
-
|
|
379
|
-
if (acao === '
|
|
380
|
-
if (acao === '
|
|
381
|
-
if (acao === '
|
|
382
|
-
if (acao === '
|
|
383
|
-
if (acao === '
|
|
384
|
-
|
|
380
|
+
// O antebraço vem ATRASADO do braço (sobreposição): a mesma onda, a fase depois.
|
|
381
|
+
if (acao === 'acenar' && dir) return { x: -0.25, z: 2.55 + Math.sin(t * 9) * 0.22, c: -0.55 + Math.sin(t * 9 - 1.1) * 0.4 };
|
|
382
|
+
if (acao === 'apontar' && dir) return { x: -1.45 + ruido(t * 0.8, sem + 3) * 0.04, z: 0.12, c: -0.05 };
|
|
383
|
+
if (acao === 'comemorar') return { x: -0.2, z: lado * (2.7 + Math.sin(t * 8 + lado) * 0.15), c: -0.25 + Math.sin(t * 8 + lado - 1) * 0.2 };
|
|
384
|
+
if (acao === 'pensar' && dir) return { x: -1.5 + ruido(t * 0.5, sem + 4) * 0.05, z: -0.35, c: -2.1 };
|
|
385
|
+
if (acao === 'dancar') return { x: -0.6 + Math.sin(t * 6 + lado) * 0.5, z: lado * (0.6 + Math.sin(t * 6 - 0.5) * 0.3), c: -1.1 + Math.sin(t * 6 + lado - 0.9) * 0.3 };
|
|
386
|
+
// Andar e correr: o braço oposto à perna, atrasado dela (natural.passo).
|
|
387
|
+
if (acao === 'correr' || acao === 'andar') {
|
|
388
|
+
const ps = passo(t, { correr: acao === 'correr', semente: sem });
|
|
389
|
+
const b = ps.braco[dir ? 1 : 0];
|
|
390
|
+
return acao === 'correr' ? { x: b * 1.2, z: lado * 0.1, c: -1.3 + b * 0.25 } : { x: b, z: lado * 0.06, c: -0.25 + Math.min(0, b) * 0.5 };
|
|
391
|
+
}
|
|
385
392
|
return null;
|
|
386
393
|
}
|
|
387
394
|
|
|
388
|
-
function BracoBloco({ lado, g, cor, pele, t, acao, k = 1 }) {
|
|
389
|
-
// Repouso: o braço caído, um tico para fora
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
function BracoBloco({ lado, g, gc = g, cor, pele, t, acao, k = 1, kc = k, ar = 0, sem = 0 }) {
|
|
396
|
+
// Repouso: o braço caído, um tico para fora — e nunca parado: ele acompanha o
|
|
397
|
+
// fôlego (abre na inspiração) e deriva pelo ruído. O cotovelo vem depois do
|
|
398
|
+
// ombro (gc e kc são o gesto e a ação de ~0,1 s antes): a sobreposição.
|
|
399
|
+
const deriva = ruido(t * 0.45, sem + lado * 5) * 0.035;
|
|
400
|
+
let ombroX = lerp(0.08, -0.85, g) + Math.sin(t * 6 + lado) * 0.06 * Math.max(0, g) + deriva;
|
|
401
|
+
let ombroZ = lado * (lerp(0.06, 0.22, Math.max(0, g)) + ar * 0.025);
|
|
402
|
+
let cotovelo = lerp(-0.12, -1.05, gc) + Math.sin(t * 7.3 + lado * 2 - 0.8) * 0.14 * Math.max(0, gc) + deriva * 0.6;
|
|
403
|
+
const pose = poseDoBraco(acao, lado, t, k, sem);
|
|
404
|
+
const poseC = poseDoBraco(acao, lado, t - 0.09, kc, sem);
|
|
405
|
+
if (pose) { ombroX = lerp(ombroX, pose.x, k); ombroZ = lerp(ombroZ, pose.z, k); }
|
|
406
|
+
if (poseC) cotovelo = lerp(cotovelo, poseC.c, kc);
|
|
395
407
|
return (
|
|
396
408
|
<group position={[lado * 0.47, 1.4, 0]} rotation={[ombroX, 0, ombroZ]}>
|
|
397
409
|
<Bloco s={[0.2, 0.36, 0.2]} p={[0, -0.17, 0]} c={cor} r={0.04} rough={0.8} />
|
|
@@ -404,42 +416,65 @@ function BracoBloco({ lado, g, cor, pele, t, acao, k = 1 }) {
|
|
|
404
416
|
}
|
|
405
417
|
|
|
406
418
|
function PessoaBloco({ p, est }) {
|
|
407
|
-
const
|
|
419
|
+
const t = est.t, sem = est.semente || 0;
|
|
420
|
+
// O fôlego: inspira rápido, expira devagar, o ritmo varia (natural.respiracao).
|
|
421
|
+
const ar = respiracao(t, sem);
|
|
422
|
+
const resp = ar * 0.012;
|
|
408
423
|
const [cy, cp, cr] = est.cabeca;
|
|
409
424
|
const calca = escurecer(p.roupa, 0.35);
|
|
410
425
|
const a = est.acao, ka = est.forcaAcao || 0;
|
|
411
|
-
// O corpo inteiro
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
426
|
+
// O corpo inteiro. Pular e comemorar: agacha, estica, cai acelerando e amassa
|
|
427
|
+
// no pouso (natural.salto). Dançar: o quadril leva, o tronco e a cabeça vêm
|
|
428
|
+
// atrasados. Andar e correr: o passo com quadril, contato e contragiro.
|
|
429
|
+
const pula = a === 'pular' || a === 'comemorar';
|
|
430
|
+
const sl = pula ? salto(t, a === 'comemorar' ? { periodo: 0.8, altura: 0.22 } : { periodo: 0.95, altura: 0.36 }) : null;
|
|
431
|
+
const anda = a === 'andar' || a === 'correr';
|
|
432
|
+
const ps = anda ? passo(t, { correr: a === 'correr', semente: sem }) : null;
|
|
433
|
+
const danca = a === 'dancar';
|
|
434
|
+
// Parado, o peso troca de perna devagar: nunca congelado.
|
|
435
|
+
const peso = ruido(t * 0.22, sem + 11) * 0.018;
|
|
436
|
+
const y = (sl ? sl.y * ka : 0) + (ps ? ps.sobe * ka : 0) + (danca ? (0.5 + 0.5 * Math.cos(t * 12)) * 0.04 * ka : 0);
|
|
437
|
+
const sy = sl ? lerp(1, sl.sy, Math.min(1, ka)) : 1, sx = sl ? lerp(1, sl.sx, Math.min(1, ka)) : 1;
|
|
438
|
+
const quadril = peso + (ps ? ps.quadril * ka : 0) + (danca ? Math.sin(t * 6) * 0.14 * ka : 0);
|
|
439
|
+
// O tronco gira contra o quadril (andar) ou vem atrasado dele (dançar).
|
|
440
|
+
const troncoZ = danca ? -Math.sin(t * 6 - 0.6) * 0.07 * ka : -peso * 0.5;
|
|
441
|
+
const troncoY = ps ? ps.giro * ka : 0;
|
|
442
|
+
const troncoX = (ps ? ps.inclina * ka : 0) + ar * -0.006;
|
|
443
|
+
// A cabeça segura o olhar: desfaz parte do giro do tronco, e balança por último.
|
|
444
|
+
const cabecaZ = cr + (danca ? Math.sin(t * 6 - 1.2) * 0.08 * ka : 0) - quadril * 0.4;
|
|
415
445
|
return (
|
|
416
|
-
<group position={[0,
|
|
446
|
+
<group position={[0, y, 0]} rotation={[0, 0, quadril]} scale={[sx, sy, sx]}>
|
|
417
447
|
{/* as pernas e o sapato */}
|
|
418
448
|
{[-1, 1].map((k) => {
|
|
419
|
-
const
|
|
449
|
+
const perna = ps ? ps.perna[k > 0 ? 1 : 0] * ka : 0;
|
|
420
450
|
return (
|
|
421
|
-
<group key={k} position={[k * 0.15, 0.76, 0]} rotation={[
|
|
451
|
+
<group key={k} position={[k * 0.15, 0.76, 0]} rotation={[perna, 0, -quadril * 0.8]}>
|
|
422
452
|
<Bloco s={[0.25, 0.72, 0.27]} p={[0, -0.36, 0]} c={calca} r={0.03} rough={0.85} />
|
|
423
453
|
<Bloco s={[0.27, 0.1, 0.36]} p={[0, -0.71, 0.04]} c="#1a1a1f" r={0.03} />
|
|
424
454
|
</group>
|
|
425
455
|
);
|
|
426
456
|
})}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
457
|
+
{/* do quadril para cima: o tronco, os braços e a cabeça giram juntos no quadril */}
|
|
458
|
+
<group position={[0, 0.76, 0]} rotation={[troncoX, troncoY, troncoZ]}>
|
|
459
|
+
<group position={[0, -0.76, 0]}>
|
|
460
|
+
<group position={[0, resp, 0]} scale={[1 + ar * 0.008, 1 + ar * 0.014, 1 + ar * 0.02]}>
|
|
461
|
+
{/* o tronco, com a faixa branca da camisa da referência */}
|
|
462
|
+
<Bloco s={[0.76, 0.66, 0.4]} p={[0, 1.1, 0]} c={p.roupa} r={0.05} rough={0.8} />
|
|
463
|
+
<Bloco s={[0.06, 0.5, 0.02]} p={[0.17, 1.12, 0.205]} c="#dcd8d0" r={0.008} sombra={false} />
|
|
464
|
+
<Bloco s={[0.22, 0.08, 0.22]} p={[0, 1.46, 0]} c={escurecer(p.pele, 0.88)} r={0.02} />
|
|
465
|
+
</group>
|
|
466
|
+
<BracoBloco lado={-1} g={est.gesto[0]} gc={(est.gestoAtras || est.gesto)[0]} cor={p.roupa} pele={p.pele} t={t} acao={a} k={ka} kc={est.forcaAtras ?? ka} ar={ar} sem={sem} />
|
|
467
|
+
<BracoBloco lado={1} g={est.gesto[1]} gc={(est.gestoAtras || est.gesto)[1]} cor={p.roupa} pele={p.pele} t={t} acao={a} k={ka} kc={est.forcaAtras ?? ka} ar={ar} sem={sem} />
|
|
468
|
+
<group position={[0, 1.8 + resp * 1.2, 0]} rotation={[cp - troncoX * 0.6, cy - troncoY * 0.6, cabecaZ]}>
|
|
469
|
+
{p.cabeca !== 'facetada' ? (
|
|
470
|
+
<>
|
|
471
|
+
<Bloco s={[CAB, CAB, CAB]} c={p.pele} r={0.04} rough={0.6} />
|
|
472
|
+
<RostoPixel p={p} est={est} />
|
|
473
|
+
<Cabelo estilo={p.cabelo} cor={p.corCabelo} />
|
|
474
|
+
</>
|
|
475
|
+
) : <CabecaFacetada p={p} est={est} />}
|
|
476
|
+
</group>
|
|
477
|
+
</group>
|
|
443
478
|
</group>
|
|
444
479
|
</group>
|
|
445
480
|
);
|
|
@@ -454,15 +489,20 @@ function PessoaBloco({ p, est }) {
|
|
|
454
489
|
function Mascote({ p, est }) {
|
|
455
490
|
const cor = p.cor || '#d9532b';
|
|
456
491
|
const [abre] = est.boca;
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const
|
|
492
|
+
const ka = Math.min(1, est.forcaAcao || 0);
|
|
493
|
+
// O pulo com peso: agacha, estica no impulso, cai acelerando e amassa no pouso (natural.salto).
|
|
494
|
+
const sl = (est.acao === 'pular' || est.acao === 'comemorar') ? salto(est.t, { periodo: 0.85, altura: 0.3 }) : { y: 0, sx: 1, sy: 1 };
|
|
495
|
+
const pulo = sl.y * ka;
|
|
496
|
+
const respira = (respiracao(est.t, (est.semente || 0) + 0.7) - 0.5) * 0.024;
|
|
497
|
+
const estica = (1 + abre * 0.07 + respira) * lerp(1, sl.sy, ka), achata = (1 - abre * 0.035) * lerp(1, sl.sx, ka);
|
|
460
498
|
const pisca = clamp(est.pisca);
|
|
461
499
|
const [cy, cp, cr] = est.cabeca;
|
|
462
500
|
const W = 1.0, H = 0.92, D = 0.78, perna = 0.46;
|
|
463
501
|
const pernas = [-0.38, -0.19, 0, 0.19, 0.38];
|
|
502
|
+
// O corpo balança de leve sobre as perninhas, atrasado da cabeça (sobreposição).
|
|
503
|
+
const ginga = ruido(est.t * 0.3, (est.semente || 0) + 21) * 0.025;
|
|
464
504
|
return (
|
|
465
|
-
<group position={[0, pulo, 0]} rotation={[0, cy * 0.8, cr * 0.5]}>
|
|
505
|
+
<group position={[0, pulo, 0]} rotation={[0, cy * 0.8, cr * 0.5 + ginga]}>
|
|
466
506
|
{pernas.map((x, i) => (
|
|
467
507
|
<group key={i}>
|
|
468
508
|
<Bloco s={[0.13, perna, 0.13]} p={[x, perna / 2 + Math.max(0, Math.sin(est.t * 9 + i)) * (est.falando ? 0.015 : 0), D / 2 - 0.12]} c={escurecer(cor, 0.9)} r={0.015} rough={0.6} />
|
|
@@ -804,33 +844,58 @@ export function VoxelCena({ el, t }) {
|
|
|
804
844
|
for (const f of falas) if (f.quem === i && f.de <= t + 0.2) e = t <= f.ate + 2.5 ? f.emocao : 'neutro';
|
|
805
845
|
return e;
|
|
806
846
|
};
|
|
807
|
-
// A ação da fala vale da voz até 0,6 s depois
|
|
847
|
+
// A ação da fala vale da voz até 0,6 s depois: entra em mola (passa do ponto e
|
|
848
|
+
// assenta) e sai devagar. O cotovelo pega a mesma ação ~0,1 s depois (sobreposição).
|
|
808
849
|
const acaoDe = (i) => {
|
|
809
850
|
for (const f of falas) {
|
|
810
851
|
if (f.quem !== i || !f.acao || f.acao === 'falar') continue;
|
|
811
|
-
if (t >= f.de - 0.3 && t <= f.ate + 0.6)
|
|
852
|
+
if (t >= f.de - 0.3 && t <= f.ate + 0.6) {
|
|
853
|
+
const opc = { entra: 2.2, sai: 0.5, amort: 0.55 };
|
|
854
|
+
return [f.acao, envelope(t, f.de - 0.3, f.ate + 0.1, opc), envelope(t, f.de - 0.3, f.ate + 0.1, { ...opc, atraso: 0.09 })];
|
|
855
|
+
}
|
|
812
856
|
}
|
|
813
|
-
return [null, 0];
|
|
857
|
+
return [null, 0, 0];
|
|
814
858
|
};
|
|
815
|
-
|
|
859
|
+
// O quanto cada um está falando (0 a 1, sem degrau): o aceno de cabeça de quem
|
|
860
|
+
// fala e o de quem ouve trocam com mola, não num corte seco.
|
|
861
|
+
const pesoDaFala = (i, reacao = 0) => falas.reduce((m, f) => (f.quem === i ? Math.max(m, Math.min(1, envelope(t, f.de + reacao, f.ate, { entra: 2.6, sai: 0.45, amort: 0.7 }))) : m), 0);
|
|
816
862
|
const estado = (i, figurante = false) => {
|
|
863
|
+
const sem = i * 13 + (figurante ? 101 : 1);
|
|
817
864
|
const fala = !figurante && falandoAgora(i);
|
|
818
|
-
const
|
|
819
|
-
const outro = qf >= 0 && qf !== i;
|
|
865
|
+
const wFala = figurante ? 0 : pesoDaFala(i);
|
|
820
866
|
const enfase = figurante ? [0, 0] : gesto(falas, i, t);
|
|
821
|
-
const
|
|
867
|
+
const enfaseAtras = figurante ? [0, 0] : gesto(falas, i, t - 0.09);
|
|
868
|
+
// Quem ouve vira para quem fala com ~0,2 s de reação e mola, e volta devagar.
|
|
869
|
+
let paraQuemFala = 0, wOuve = 0, dx = 0;
|
|
870
|
+
if (!figurante) {
|
|
871
|
+
for (const f of falas) {
|
|
872
|
+
if (f.quem === i) continue;
|
|
873
|
+
const w = Math.min(1, envelope(t, f.de + 0.2, f.ate + 0.3, { entra: 1.6, sai: 0.7, amort: 0.62 }));
|
|
874
|
+
if (w > wOuve) { wOuve = w; dx = lugarDe(elenco, f.quem)[0] - lugarDe(elenco, i)[0]; }
|
|
875
|
+
}
|
|
876
|
+
paraQuemFala = clamp(dx * 0.35, -0.35, 0.35) * wOuve;
|
|
877
|
+
}
|
|
878
|
+
const nodFala = Math.sin(t * 5.2 + i) * 0.03 + ruido(t * 2.4, sem + 2) * 0.02;
|
|
879
|
+
const nodQuieto = ruido(t * 0.5, sem + 3) * 0.018;
|
|
880
|
+
const nod = lerp(nodQuieto, nodFala, wFala) + wOuve * Math.max(0, Math.sin(t * 2.1 + i)) * 0.04;
|
|
822
881
|
const emo = figurante ? 'feliz' : emocaoDe(i);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
882
|
+
// A inclinação da emoção chega e vai embora com peso, não troca no quadro.
|
|
883
|
+
let inclina = 0;
|
|
884
|
+
if (!figurante) {
|
|
885
|
+
for (const f of falas) {
|
|
886
|
+
if (f.quem !== i || f.de > t + 0.2) continue;
|
|
887
|
+
const ef = EMO[f.emocao] || EMO.neutro;
|
|
888
|
+
inclina = (ef.inclina || 0) * Math.min(1.05, envelope(t, f.de - 0.2, f.ate + 2.5, { entra: 1.4, sai: 0.8, amort: 0.6 }));
|
|
889
|
+
}
|
|
890
|
+
} else inclina = (EMO.feliz.inclina || 0);
|
|
891
|
+
const micro = (hash(Math.floor(t * 1.5 + hash(i) * 0.9) + i * 7) - 0.5) * 0.6;
|
|
892
|
+
const [acao, forcaAcao, forcaAtras] = figurante ? [null, 0, 0] : acaoDe(i);
|
|
829
893
|
return {
|
|
830
|
-
t, falando: fala, emocao: emo, boca: figurante ? [0, 0.55] : boca(falas, i, t), pisca: piscada(i + (figurante ? 5 : 0), t),
|
|
831
|
-
gesto: enfase, enfase: Math.max(enfase[0], enfase[1]), acao, forcaAcao,
|
|
832
|
-
olhar: [micro +
|
|
833
|
-
|
|
894
|
+
t, semente: sem, falando: fala, emocao: emo, boca: figurante ? [0, 0.55] : boca(falas, i, t), pisca: piscada(i + (figurante ? 5 : 0), t),
|
|
895
|
+
gesto: enfase, gestoAtras: enfaseAtras, enfase: Math.max(enfase[0], enfase[1], 0), acao, forcaAcao, forcaAtras,
|
|
896
|
+
olhar: [micro + Math.sign(dx) * 0.8 * wOuve, 0.1],
|
|
897
|
+
// A cabeça deriva pelo ruído (nunca a mesma volta) e vem ~0,06 s atrás do corpo.
|
|
898
|
+
cabeca: [ruido((t - 0.06) * 0.35, sem + 5) * 0.08 + paraQuemFala, nod, inclina * (i % 2 ? -1 : 1) + ruido((t - 0.06) * 0.25, sem + 7) * 0.025],
|
|
834
899
|
};
|
|
835
900
|
};
|
|
836
901
|
const desenhar = (p, est) => (p.tipo === 'robo' ? <Mascote p={p} est={est} />
|
package/lib/estudio/kit.js
CHANGED
|
@@ -67,7 +67,7 @@ const PECAS = {
|
|
|
67
67
|
animacao: { precisa: ['src'] },
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'index.jsx'];
|
|
70
|
+
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'natural.js', 'index.jsx'];
|
|
71
71
|
|
|
72
72
|
/** Os arquivos do kit, para o projeto Remotion: [{nome: 'kit/base.jsx', conteudo}]. */
|
|
73
73
|
function arquivos() {
|
package/lib/estudio/revisor.js
CHANGED
|
@@ -67,7 +67,10 @@ function revisar(plano, { semImagem = false } = {}) {
|
|
|
67
67
|
const efeitos = ((plano.audio && plano.audio.efeitos) || []).slice().sort((a, b) => (a.t || 0) - (b.t || 0));
|
|
68
68
|
if (efeitos.length) {
|
|
69
69
|
const fica = [];
|
|
70
|
-
|
|
70
|
+
/* O micro (o tique, a escada de notas) não conta para o teto: é a
|
|
71
|
+
textura das referências, 2 a 3 por segundo, baixinho (14/10). */
|
|
72
|
+
const micros = efeitos.filter((e) => e.micro);
|
|
73
|
+
for (const e of efeitos.filter((x) => !x.micro)) {
|
|
71
74
|
const ult = fica[fica.length - 1];
|
|
72
75
|
if (ult && e.src === ult.src && (e.t || 0) - (ult.t || 0) < 0.3) {
|
|
73
76
|
if ((e.volume ?? 1) > (ult.volume ?? 1)) fica[fica.length - 1] = e;
|
|
@@ -88,7 +91,7 @@ function revisar(plano, { semImagem = false } = {}) {
|
|
|
88
91
|
final = guardados.sort((a, b) => (a.t || 0) - (b.t || 0));
|
|
89
92
|
consertos.push(`${fica.length} efeitos sonoros em ${r2(total)}s eram ruído — ficaram os ${final.length} mais fortes`);
|
|
90
93
|
}
|
|
91
|
-
plano.audio.efeitos = final;
|
|
94
|
+
plano.audio.efeitos = [...final, ...micros].sort((a, b) => (a.t || 0) - (b.t || 0));
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
// 4. Texto que não dá tempo de ler.
|