primocode 8.42.2 → 9.0.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.
Files changed (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +598 -360
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
@@ -1,607 +0,0 @@
1
- /* ==========================================================================
2
- audio.js — trilha sonora do Corte.
3
-
4
- Dois caminhos:
5
- tipo "arquivo" → toca um áudio seu (upload em /midia ou URL direta)
6
- tipo "gerada" → o estúdio sintetiza a música na hora, sem arquivo
7
-
8
- Tudo passa por um nó de saída único para poder ser gravado junto do vídeo.
9
-
10
- ── POR QUE A MÚSICA GERADA FOI REFEITA ─────────────────────────────────
11
-
12
- "Música de fundo: escolha de músicas ruim, sem qualidade."
13
-
14
- A versão anterior tinha um defeito que não se ouve em cinco segundos e não
15
- se aguenta em dois minutos: TODO COMPASSO ERA IDÊNTICO. O arpejo era uma
16
- sequência fixa de oito notas, a bateria era a mesma batida, o pad era o
17
- mesmo acorde, e o filtro não se mexia. Numa peça de trinta segundos aquilo
18
- é um loop de dois segundos repetido quinze vezes — e é isso que se ouve
19
- como "sem qualidade", antes de qualquer discussão sobre timbre.
20
-
21
- Junto vinham três coisas que separam som de programa de som de música:
22
-
23
- · TUDO SECO. Oscilador → filtro → saída. Sem espaço nenhum, qualquer
24
- síntese soa como toque de celular antigo. Reverberação é a diferença
25
- maior por linha de código deste arquivo.
26
- · TUDO NO CENTRO. Mono. Um pad largo em estéreo soa "produzido" e um pad
27
- mono soa "gerado", com as mesmas notas.
28
- · TUDO NA MESMA FORÇA. Nota humana varia de volume e chega alguns
29
- milissegundos fora do quadrado; nota de máquina não varia nunca.
30
-
31
- O que NÃO entrou, e é honesto dizer: nada disso vira música boa de verdade.
32
- São quatro osciladores e um ruído. O alvo aqui é a cama sob uma narração —
33
- presente, variada e discreta —, não uma faixa para ouvir sozinha.
34
-
35
- ── O SORTEIO É SEMEADO, E ISSO É OBRIGATÓRIO ───────────────────────────
36
- A variação toda sai de `_sorteio`, um gerador determinístico semeado pelo
37
- estilo. Com `Math.random` a mesma peça sairia com uma música diferente a
38
- cada vez que fosse tocada ou exportada — a pessoa aprova a prévia, exporta,
39
- e recebe outro vídeo. Aqui o mesmo estilo dá sempre a mesma faixa.
40
- ========================================================================== */
41
-
42
- var ESTILOS_TRILHA = {
43
- pulso: {
44
- rotulo: "Pulso", bpm: 112, raiz: 45,
45
- escala: [0, 2, 3, 5, 7, 8, 10],
46
- progressao: [0, 5, 3, 4],
47
- bateria: true, ondaPad: "sawtooth", ondaArp: "square",
48
- corte: 1600, ganhoPad: 0.05, ganhoArp: 0.045, arpDiv: 2,
49
- largura: 0.4, reverb: 0.22, setima: true, bombeia: true,
50
- },
51
- ambiente: {
52
- rotulo: "Ambiente", bpm: 68, raiz: 41,
53
- escala: [0, 2, 4, 7, 9],
54
- progressao: [0, 3, 5, 3],
55
- bateria: false, ondaPad: "sawtooth", ondaArp: "sine",
56
- corte: 900, ganhoPad: 0.08, ganhoArp: 0.026, arpDiv: 1,
57
- largura: 0.6, reverb: 0.5, setima: true,
58
- },
59
- lofi: {
60
- rotulo: "Lo-fi", bpm: 76, raiz: 43,
61
- escala: [0, 2, 3, 5, 7, 8, 10],
62
- progressao: [0, 3, 4, 2],
63
- bateria: true, ondaPad: "triangle", ondaArp: "sine",
64
- corte: 1100, ganhoPad: 0.065, ganhoArp: 0.038, arpDiv: 2, solto: true,
65
- largura: 0.35, reverb: 0.34, setima: true,
66
- },
67
- epico: {
68
- rotulo: "Épico", bpm: 88, raiz: 38,
69
- escala: [0, 2, 3, 5, 7, 8, 10],
70
- progressao: [0, 5, 6, 4],
71
- bateria: true, ondaPad: "sawtooth", ondaArp: "triangle",
72
- corte: 2200, ganhoPad: 0.085, ganhoArp: 0.05, arpDiv: 2, oitavaBaixo: true,
73
- largura: 0.5, reverb: 0.42, setima: true, bombeia: true,
74
- },
75
- alegre: {
76
- rotulo: "Alegre", bpm: 124, raiz: 48,
77
- escala: [0, 2, 4, 5, 7, 9, 11],
78
- progressao: [0, 4, 5, 3],
79
- bateria: true, ondaPad: "triangle", ondaArp: "square",
80
- corte: 2600, ganhoPad: 0.045, ganhoArp: 0.04, arpDiv: 4,
81
- largura: 0.45, reverb: 0.2, bombeia: true,
82
- },
83
- tenso: {
84
- rotulo: "Tenso", bpm: 100, raiz: 36,
85
- escala: [0, 1, 3, 5, 6, 8, 10],
86
- progressao: [0, 0, 1, 0],
87
- bateria: true, ondaPad: "sawtooth", ondaArp: "sawtooth",
88
- corte: 700, ganhoPad: 0.07, ganhoArp: 0.032, arpDiv: 4,
89
- largura: 0.3, reverb: 0.3, setima: true,
90
- },
91
- /* As duas de baixo entraram junto com a reforma, e por um motivo de uso:
92
- a maior parte dos vídeos deste estúdio é gente falando na frente da
93
- câmera. `epico` e `tenso` competem com a voz; para uma narração o que
94
- serve é uma cama que quase não se nota. Sem elas, o agente escolhia entre
95
- seis estilos e cinco eram grandes demais para o vídeo que ele estava
96
- montando. */
97
- /* `suave` mora BAIXO de propósito (raiz 40, corte 1000): a fala vive dos
98
- 300 aos 3500 Hz, e uma cama nesse registro disputa com a narração por mais
99
- que o volume esteja baixo. Medido: com a raiz em 46 ela ocupava mais a
100
- faixa da voz que o `epico`, que é uma faixa feita para ser grande. */
101
- suave: {
102
- rotulo: "Suave", bpm: 60, raiz: 40,
103
- escala: [0, 2, 4, 7, 9],
104
- progressao: [0, 4, 2, 5],
105
- bateria: false, ondaPad: "sine", ondaArp: "sine",
106
- corte: 1000, ganhoPad: 0.042, ganhoArp: 0.016, arpDiv: 2,
107
- largura: 0.65, reverb: 0.5, setima: true,
108
- },
109
- foco: {
110
- rotulo: "Foco", bpm: 96, raiz: 44,
111
- escala: [0, 2, 4, 5, 7, 9],
112
- progressao: [0, 2, 4, 2],
113
- bateria: true, ondaPad: "triangle", ondaArp: "sine",
114
- corte: 1500, ganhoPad: 0.036, ganhoArp: 0.02, arpDiv: 2, semCaixa: true,
115
- largura: 0.6, reverb: 0.28, setima: true, discreta: true,
116
- },
117
- };
118
-
119
- function nomesEstilosTrilha() { return Object.keys(ESTILOS_TRILHA); }
120
-
121
- function _hz(midi) { return 440 * Math.pow(2, (midi - 69) / 12); }
122
-
123
- /* Sorteio determinístico (mulberry32). Ver o cabeçalho: com Math.random a
124
- prévia aprovada e o arquivo exportado seriam duas músicas diferentes. */
125
- function _sorteio(semente) {
126
- var a = semente >>> 0;
127
- return function () {
128
- a = (a + 0x6D2B79F5) >>> 0;
129
- var t = Math.imul(a ^ (a >>> 15), 1 | a);
130
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
131
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
132
- };
133
- }
134
-
135
- function _semeadoPor(texto) {
136
- var h = 2166136261;
137
- for (var i = 0; i < String(texto).length; i++) {
138
- h ^= String(texto).charCodeAt(i);
139
- h = Math.imul(h, 16777619);
140
- }
141
- return h >>> 0;
142
- }
143
-
144
- /* A sala. Ruído decaindo é a forma mais barata de impulso que soa como espaço
145
- de verdade; o pré-atraso de 20ms é o que separa "reverberação" de "borrão"
146
- — sem ele o primeiro reflexo chega junto com a nota e engrossa o ataque. */
147
- function _impulso(ctx, segundos, decaimento, rnd) {
148
- var n = Math.max(1, Math.floor(ctx.sampleRate * segundos));
149
- var buf = ctx.createBuffer(2, n, ctx.sampleRate);
150
- for (var c = 0; c < 2; c++) {
151
- var d = buf.getChannelData(c);
152
- for (var i = 0; i < n; i++) {
153
- d[i] = (rnd() * 2 - 1) * Math.pow(1 - i / n, decaimento);
154
- }
155
- }
156
- return buf;
157
- }
158
-
159
-
160
- function criarTrilha(cfg, aoErro, ctxDado) {
161
- cfg = cfg || {};
162
- var ctx = ctxDado;
163
- if (!ctx) {
164
- var Ctx = window.AudioContext || window.webkitAudioContext;
165
- if (!Ctx) return null;
166
- ctx = new Ctx();
167
- }
168
- var mestre = ctx.createGain();
169
- var volumeBase = cfg.volume != null ? cfg.volume : 0.7;
170
- mestre.gain.value = volumeBase;
171
-
172
- var compressor = ctx.createDynamicsCompressor();
173
- compressor.threshold.value = -16;
174
- compressor.ratio.value = 3;
175
- compressor.attack.value = 0.006;
176
- compressor.release.value = 0.22;
177
- mestre.connect(compressor);
178
- compressor.connect(ctx.destination);
179
-
180
- /* Saída paralela: é daqui que a gravação puxa o som. O contexto OFFLINE (o
181
- do render de teste) não tem `createMediaStreamDestination`, e chamá-lo ali
182
- derrubaria a trilha inteira num TypeError — antes de tocar uma nota. */
183
- var paraGravar = ctx.createMediaStreamDestination ? ctx.createMediaStreamDestination() : null;
184
- if (paraGravar) compressor.connect(paraGravar);
185
-
186
- /* Entrada da NARRAÇÃO: depois do `mestre`, antes do compressor. A voz não
187
- passa pelo volume da trilha nem pelo duck — quem abaixa é a música, nunca
188
- a fala. E como desemboca no mesmo compressor, a voz entra na gravação
189
- MP4 pelo grafo, sem depender de "compartilhar áudio da aba". */
190
- var entradaVoz = ctx.createGain();
191
- entradaVoz.gain.value = 1;
192
- entradaVoz.connect(compressor);
193
-
194
- var api = {
195
- ctx: ctx,
196
- no: compressor, // onde SFX e outras fontes se conectam
197
- entradaVoz: entradaVoz,
198
- fluxo: paraGravar ? paraGravar.stream : null,
199
- tocando: false,
200
- tocar: function () {},
201
- parar: function () {},
202
- volume: function (v) { volumeBase = v; mestre.gain.value = v; },
203
- esmaecer: function () {},
204
- duracao: function () { return null; },
205
- /* Ducking: a trilha cede espaço enquanto a narração fala.
206
- Rampa curta para descer (a voz não espera) e longa para voltar. */
207
- duck: function (ligado) {
208
- var agora = ctx.currentTime;
209
- mestre.gain.cancelScheduledValues(agora);
210
- mestre.gain.setValueAtTime(mestre.gain.value, agora);
211
- mestre.gain.linearRampToValueAtTime(
212
- ligado ? volumeBase * 0.35 : volumeBase,
213
- agora + (ligado ? 0.15 : 0.5));
214
- },
215
- };
216
-
217
- function retomar() {
218
- if (ctx.state === "suspended" && ctx.resume) ctx.resume();
219
- }
220
-
221
- /* -------------------------------------------------------- sem trilha ---- */
222
- /* tipo "nenhuma": o documento não tem música, mas o grafo ainda serve —
223
- é por ele que narração e SFX entram na gravação. */
224
- if (cfg.tipo === "nenhuma") {
225
- api.tocar = function () { retomar(); api.tocando = true; };
226
- api.parar = function () { api.tocando = false; };
227
- api.agendarAte = function () {};
228
- return api;
229
- }
230
-
231
- /* --------------------------------------------------------- arquivo ---- */
232
-
233
- if (cfg.tipo !== "gerada" && cfg.src) {
234
- var el = new Audio();
235
- el.crossOrigin = "anonymous";
236
- el.src = cfg.src;
237
- el.loop = cfg.repetir !== false;
238
- el.preload = "auto";
239
- el.onerror = function () { if (aoErro) aoErro("não consegui carregar o áudio"); };
240
-
241
- var fonte = null;
242
- try {
243
- fonte = ctx.createMediaElementSource(el);
244
- fonte.connect(mestre);
245
- } catch (_) {
246
- el.volume = mestre.gain.value; // sem Web Audio: toca direto mesmo
247
- }
248
-
249
- api.elemento = el;
250
- api.tocar = function () {
251
- retomar();
252
- el.currentTime = cfg.inicio || 0;
253
- var p = el.play();
254
- if (p && p.catch) p.catch(function () { if (aoErro) aoErro("o navegador bloqueou o áudio; clique na tela"); });
255
- api.tocando = true;
256
- };
257
- api.parar = function () { el.pause(); api.tocando = false; };
258
- api.duracao = function () { return isFinite(el.duration) ? el.duration : null; };
259
- api.esmaecer = function (segundos) {
260
- var agora = ctx.currentTime;
261
- mestre.gain.cancelScheduledValues(agora);
262
- mestre.gain.setValueAtTime(mestre.gain.value, agora);
263
- mestre.gain.linearRampToValueAtTime(0.0001, agora + (segundos || 1.2));
264
- };
265
- return api;
266
- }
267
-
268
- /* ---------------------------------------------------------- gerada ---- */
269
-
270
- var E = ESTILOS_TRILHA[cfg.estilo] || ESTILOS_TRILHA.pulso;
271
- var tempoBatida = 60 / E.bpm;
272
- var passo = 0, proximo = 0, relogio = null;
273
- var rnd = _sorteio(_semeadoPor(cfg.estilo || "pulso"));
274
-
275
- /* O barramento da música existe por causa do BOMBEIO: é ele que abaixa no
276
- bumbo. Sem um nó só para os instrumentos, o duck da narração e o bombeio
277
- escreveriam no MESMO ganho — o segundo a agendar cancelaria o primeiro, e
278
- a voz ficaria por baixo da música na primeira batida depois de falar. */
279
- var corpo = ctx.createGain();
280
- corpo.gain.value = 1;
281
- corpo.connect(mestre);
282
-
283
- /* A sala. O retorno entra no `mestre` (e não no compressor) de propósito:
284
- assim a reverberação abaixa junto no duck. Cauda vazando por cima da
285
- narração é o defeito que faz a pessoa baixar o volume da trilha inteira. */
286
- var sala = null, envio = null;
287
- if (E.reverb && ctx.createConvolver) {
288
- var conv = ctx.createConvolver();
289
- conv.buffer = _impulso(ctx, E.reverb > 0.4 ? 2.6 : 1.6, 2.4, rnd);
290
- var atraso = ctx.createDelay ? ctx.createDelay(0.2) : null;
291
- envio = ctx.createGain();
292
- envio.gain.value = E.reverb;
293
- var retorno = ctx.createGain();
294
- retorno.gain.value = 0.9;
295
- if (atraso) { atraso.delayTime.value = 0.02; envio.connect(atraso); atraso.connect(conv); }
296
- else envio.connect(conv);
297
- conv.connect(retorno);
298
- retorno.connect(mestre);
299
- sala = envio;
300
- }
301
-
302
- function panorama(quanto) {
303
- if (!ctx.createStereoPanner) return null;
304
- var p = ctx.createStereoPanner();
305
- p.pan.value = Math.max(-1, Math.min(1, quanto));
306
- return p;
307
- }
308
-
309
- /** Liga uma voz à saída, com posição no estéreo e mandada para a sala. */
310
- function ligar(no, pan, mandaSala) {
311
- var p = panorama(pan);
312
- if (p) { no.connect(p); no = p; }
313
- no.connect(corpo);
314
- if (sala && mandaSala) no.connect(sala);
315
- }
316
-
317
- function grau(n) {
318
- var oitava = Math.floor(n / E.escala.length);
319
- return E.raiz + E.escala[((n % E.escala.length) + E.escala.length) % E.escala.length] + 12 * oitava;
320
- }
321
-
322
- function nota(freq, quando, dur, onda, ganho, corte, pan, sala2, ataque) {
323
- var o = ctx.createOscillator();
324
- var g = ctx.createGain();
325
- var f = ctx.createBiquadFilter();
326
- f.type = "lowpass";
327
- f.frequency.setValueAtTime(corte || E.corte, quando);
328
- o.type = onda;
329
- o.frequency.setValueAtTime(freq, quando);
330
- g.gain.setValueAtTime(0, quando);
331
- g.gain.linearRampToValueAtTime(ganho,
332
- quando + Math.min(ataque || 0.06, dur * 0.45));
333
- g.gain.exponentialRampToValueAtTime(0.0001, quando + dur);
334
- o.connect(f); f.connect(g);
335
- ligar(g, pan || 0, sala2 !== false);
336
- o.start(quando);
337
- o.stop(quando + dur + 0.05);
338
- }
339
-
340
- /* O acorde ganhou a SÉTIMA e uma INVERSÃO por compasso.
341
- Tríade em posição fundamental, sempre a mesma, quatro compassos seguidos,
342
- é o som de exercício de teclado. A sétima dá cor e a inversão faz a nota
343
- de cima andar pouco de um acorde para o outro — que é o que o ouvido lê
344
- como "alguém tocou isso". */
345
- function pad(acorde, quando, dur, compasso, b) {
346
- var vozes = E.setima ? [0, 2, 4, 6] : [0, 2, 4];
347
- var giro = compasso % vozes.length;
348
- /* AS VOZES ENTRAM ESPALHADAS nos estilos sem bateria, e isso é o conserto
349
- de um defeito medido: sem bateria e com o acorde inteiro entrando junto
350
- no primeiro tempo, todo compasso desenha a MESMA rampa e a faixa volta a
351
- ser um loop — `ambiente` e `suave` davam dois compassos distintos em
352
- cinco. Com a entrada escalonada e um ataque diferente por compasso, o
353
- acorde "abre" de um jeito por vez, que é como um pad de verdade soa. */
354
- var espalha = E.bateria ? 0 : b.espalha;
355
- vozes.forEach(function (t, i) {
356
- var deslocada = (i + giro) % vozes.length;
357
- var oitava = deslocada < giro ? 12 : 0; // inversão: sobe quem passou
358
- var largura = E.largura || 0.35;
359
- // Voz aguda mais para fora, fundamental no centro: é assim que um pad
360
- // soa largo sem o baixo puxar para um lado.
361
- var pan = ((i / (vozes.length - 1)) - 0.5) * 2 * largura;
362
- var atraso = i * espalha;
363
- nota(_hz(grau(acorde + vozes[i]) + 12 + oitava), quando + atraso,
364
- Math.max(0.2, dur - atraso), E.ondaPad,
365
- E.ganhoPad * (i === 0 ? 1 : 0.62) * b.brilho, E.corte, pan, true, b.ataque);
366
- });
367
- // O baixo fica no centro e quase seco: grave em estéreo some em caixa de
368
- // celular, e grave com reverberação vira ronco.
369
- nota(_hz(grau(acorde) - (E.oitavaBaixo ? 12 : 0)), quando, dur, "sine",
370
- 0.1 * b.brilho * b.forca, 420, 0, false);
371
-
372
- /* SEM BATERIA, O BAIXO É QUEM MARCA O COMPASSO. Nos estilos de cama o pad
373
- decai suave por quatro segundos e a silhueta de energia de um compasso é
374
- igual à do seguinte por mais que as notas mudem — medido: `suave` dava
375
- dois compassos distintos em cinco. Uma segunda nota grave, em lugar
376
- diferente a cada compasso, é o que dá pé à cama sem pôr bateria nela. */
377
- if (!E.bateria) {
378
- var onde = quando + dur * b.apoio;
379
- nota(_hz(grau(acorde + 2) - (E.oitavaBaixo ? 12 : 0)), onde,
380
- dur * (1 - b.apoio), "sine", 0.062 * b.brilho * b.forca, 420, 0, false);
381
- }
382
- }
383
-
384
- /* Quanto a bateria pesa. `discreta` é o que faz `foco` ser cama e não faixa:
385
- a batida existe para dar andamento, e não para disputar com a voz. */
386
- var PESO = E.discreta ? 0.45 : 1;
387
-
388
- function bumbo(quando, forca) {
389
- forca *= PESO;
390
- var o = ctx.createOscillator(), g = ctx.createGain();
391
- o.type = "sine";
392
- o.frequency.setValueAtTime(150, quando);
393
- o.frequency.exponentialRampToValueAtTime(45, quando + 0.12);
394
- g.gain.setValueAtTime(0.3 * forca, quando);
395
- g.gain.exponentialRampToValueAtTime(0.0001, quando + 0.28);
396
- o.connect(g);
397
- ligar(g, 0, false);
398
- o.start(quando); o.stop(quando + 0.3);
399
-
400
- /* BOMBEIO. A música cede no golpe e volta em 250ms. É o que dá a
401
- respiração de faixa produzida — e, de quebra, abre espaço para o grave
402
- do bumbo em vez de somar tudo no mesmo instante. */
403
- if (E.bombeia) {
404
- corpo.gain.cancelScheduledValues(quando);
405
- corpo.gain.setValueAtTime(1, quando);
406
- corpo.gain.linearRampToValueAtTime(0.55, quando + 0.02);
407
- corpo.gain.linearRampToValueAtTime(1, quando + 0.26);
408
- }
409
- }
410
-
411
- var ruido = null;
412
- function bufferRuido() {
413
- if (ruido) return ruido;
414
- ruido = ctx.createBuffer(1, Math.floor(ctx.sampleRate * 0.4), ctx.sampleRate);
415
- var d = ruido.getChannelData(0);
416
- for (var i = 0; i < d.length; i++) d[i] = rnd() * 2 - 1;
417
- return ruido;
418
- }
419
-
420
- function chimbal(quando, forte, pan) {
421
- var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
422
- s.buffer = bufferRuido();
423
- f.type = "highpass";
424
- f.frequency.value = forte ? 4000 : 7000;
425
- g.gain.setValueAtTime((forte ? 0.085 : 0.04) * PESO * (0.75 + rnd() * 0.5), quando);
426
- g.gain.exponentialRampToValueAtTime(0.0001, quando + (forte ? 0.16 : 0.055));
427
- s.connect(f); f.connect(g);
428
- ligar(g, pan, true);
429
- s.start(quando); s.stop(quando + 0.25);
430
- }
431
-
432
- function caixa(quando, forca) {
433
- var s = ctx.createBufferSource(), g = ctx.createGain(), f = ctx.createBiquadFilter();
434
- s.buffer = bufferRuido();
435
- f.type = "bandpass";
436
- f.frequency.value = 1900;
437
- g.gain.setValueAtTime(0.12 * forca * PESO, quando);
438
- g.gain.exponentialRampToValueAtTime(0.0001, quando + 0.19);
439
- s.connect(f); f.connect(g);
440
- ligar(g, 0, true);
441
- s.start(quando); s.stop(quando + 0.25);
442
- }
443
-
444
- /* Os desenhos do arpejo. Quatro, e não um: é a troca deles que faz o quarto
445
- compasso não ser o primeiro de novo. */
446
- var DESENHOS = [
447
- [0, 2, 4, 2, 4, 6, 4, 2],
448
- [4, 2, 0, 2, 4, 6, 7, 6],
449
- [0, 4, 2, 6, 4, 2, 4, 0],
450
- [6, 4, 6, 2, 4, 0, 2, 4],
451
- ];
452
-
453
- /* Uma decisão por compasso, sempre a mesma para o mesmo compasso: o pump do
454
- áudio agenda com folga e um compasso pode ser calculado duas vezes se a
455
- aba engasgar. Sorteando na hora, a segunda volta daria outro desenho e a
456
- música tropeçaria em si mesma. */
457
- /* O DESENHO RÍTMICO DO ARPEJO, compasso a compasso.
458
- A grade fixa (uma nota a cada N divisões, sempre nas mesmas) é o que fazia
459
- todo compasso ter a mesma silhueta de energia. Aqui uma nota em cada
460
- quatro sai do tempo (síncope) e uma em cada sete cala — e silêncio é
461
- desenho tanto quanto nota. */
462
- function arpDaBarra(r, div, desenho) {
463
- var base = 4 / (div || 2);
464
- var mapa = new Array(16).fill(null);
465
- var i = 0;
466
- for (var k = 0; k < 16; k += base) {
467
- var pos = k;
468
- if (base > 1 && r() < 0.25) pos = Math.min(15, k + base / 2);
469
- if (r() < 0.14) { i++; continue; }
470
- mapa[pos] = desenho[i % desenho.length];
471
- i++;
472
- }
473
- return mapa;
474
- }
475
-
476
- function daBarra(compasso) {
477
- var r = _sorteio(_semeadoPor((cfg.estilo || "pulso") + ":" + compasso));
478
- var frase = Math.floor(compasso / 4);
479
- var desenho = DESENHOS[Math.floor(r() * DESENHOS.length) % DESENHOS.length];
480
- return {
481
- desenho: desenho,
482
- arp: arpDaBarra(r, E.arpDiv, desenho),
483
- // Quanto o acorde demora a abrir, e em quanto tempo cada voz entra.
484
- // Só vale nos estilos sem bateria — com bateria o acorde tem de chegar
485
- // no tempo, senão soa como erro e não como respiração.
486
- ataque: 0.08 + r() * 0.9,
487
- espalha: 0.05 + r() * 0.28,
488
- // Onde cai a segunda nota do baixo nos estilos sem bateria.
489
- apoio: 0.34 + r() * 0.4,
490
- // O filtro anda numa onda lenta de oito compassos: é o que dá a sensação
491
- // de que a música vai a algum lugar, sem mudar uma nota.
492
- brilho: 0.72 + 0.42 * (0.5 + 0.5 * Math.sin((compasso / 8) * Math.PI * 2)),
493
- // Chimbal denso alternando por frase de quatro compassos.
494
- denso: frase % 2 === 1,
495
- // Última barra da frase leva virada.
496
- virada: compasso % 4 === 3,
497
- // Um compasso em cada quatro respira sem pad novo.
498
- respira: r() < 0.18,
499
- // Volume geral da barra: gente não toca dois compassos na mesma força.
500
- forca: 0.86 + r() * 0.28,
501
- };
502
- }
503
-
504
- function agendarPasso(p, quando) {
505
- var naBarra = p % 16;
506
- var compasso = Math.floor(p / 16);
507
- var acorde = E.progressao[compasso % E.progressao.length];
508
- var b = daBarra(compasso);
509
- // Humanização: alguns milissegundos fora do quadrado. Zero para o compasso
510
- // que começa, senão o pad entra atrasado e soa como erro, não como gente.
511
- var tremor = naBarra === 0 ? 0 : (rnd() - 0.5) * 0.016;
512
- var t = quando + tremor;
513
-
514
- if (naBarra === 0 && !b.respira) {
515
- pad(acorde, quando, tempoBatida * 3.9, compasso, b);
516
- }
517
-
518
- // arpejo, na posição que ESTE compasso pediu
519
- if (b.arp[naBarra] != null) {
520
- // Ping-pong: as notas alternam de lado. Um arpejo no centro some atrás
521
- // do pad; alternando, ele aparece sem precisar de volume.
522
- var pan = (naBarra % 4 < 2 ? -1 : 1) * (E.largura || 0.35) * 0.8;
523
- nota(_hz(grau(acorde + b.arp[naBarra]) + 12), t,
524
- tempoBatida * 0.55, E.ondaArp,
525
- E.ganhoArp * b.forca * (0.8 + rnd() * 0.4), E.corte * 1.4 * b.brilho, pan);
526
- }
527
-
528
- if (E.bateria) {
529
- var atraso = E.solto ? 0.018 : 0; // lo-fi: levada atrasada
530
- if (naBarra % 8 === 0) bumbo(quando, b.forca);
531
- // Bumbo de apoio na virada: é o empurrão para o compasso seguinte.
532
- if (b.virada && naBarra === 14) bumbo(quando + atraso, b.forca * 0.8);
533
- if (!E.semCaixa) {
534
- if (naBarra === 4 || naBarra === 12) caixa(quando + atraso, b.forca);
535
- if (b.virada && (naBarra === 13 || naBarra === 15)) {
536
- caixa(quando + atraso, b.forca * 0.55);
537
- }
538
- }
539
- var cada = b.denso ? 1 : 2;
540
- if (naBarra % cada === 0) {
541
- chimbal(t + atraso, naBarra % 8 === 4, ((naBarra % 4) / 3 - 0.5) * 0.5);
542
- }
543
- }
544
- }
545
-
546
- function bombear() {
547
- while (proximo < ctx.currentTime + 0.2) {
548
- agendarPasso(passo, proximo);
549
- proximo += tempoBatida / 4;
550
- passo++;
551
- }
552
- }
553
-
554
- api.tocar = function () {
555
- if (api.tocando) return;
556
- retomar();
557
- passo = 0;
558
- proximo = ctx.currentTime + 0.08;
559
- api.tocando = true;
560
- bombear();
561
- relogio = setInterval(bombear, 25);
562
- };
563
- api.parar = function () {
564
- api.tocando = false;
565
- clearInterval(relogio);
566
- };
567
- api.esmaecer = function (segundos) {
568
- var agora = ctx.currentTime;
569
- mestre.gain.cancelScheduledValues(agora);
570
- mestre.gain.setValueAtTime(mestre.gain.value, agora);
571
- mestre.gain.linearRampToValueAtTime(0.0001, agora + (segundos || 1.2));
572
- };
573
-
574
- /* Agenda a faixa inteira de uma vez, para render offline. É o que permite
575
- MEDIR a música num teste em vez de olhar o código e achar que variou. */
576
- api.agendarAte = function (segundos) {
577
- passo = 0; proximo = 0;
578
- while (proximo < segundos) {
579
- agendarPasso(passo, proximo);
580
- proximo += tempoBatida / 4;
581
- passo++;
582
- }
583
- };
584
-
585
- return api;
586
- }
587
-
588
- /* Renderiza a trilha sem tocar, num contexto offline. Só o teste chama isto —
589
- e é por ele que "a música varia" deixa de ser opinião. */
590
- function renderizarTrilha(estilo, segundos, taxa) {
591
- var Off = window.OfflineAudioContext || window.webkitOfflineAudioContext;
592
- if (!Off) return Promise.reject(new Error("sem OfflineAudioContext"));
593
- taxa = taxa || 44100;
594
- var ctx = new Off(2, Math.round(taxa * segundos), taxa);
595
- var t = criarTrilha({ tipo: "gerada", estilo: estilo, volume: 0.9 }, null, ctx);
596
- if (!t) return Promise.reject(new Error("não montou a trilha"));
597
- t.agendarAte(segundos);
598
- return ctx.startRendering();
599
- }
600
-
601
- if (typeof module !== "undefined" && module.exports) {
602
- module.exports = {
603
- criarTrilha: criarTrilha,
604
- ESTILOS_TRILHA: ESTILOS_TRILHA,
605
- nomesEstilosTrilha: nomesEstilosTrilha,
606
- };
607
- }