primocode 10.15.2 → 10.17.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/LICENSES/edit-ia-pro.txt +32 -0
- package/LICENSES/fontes-OFL.txt +476 -0
- package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
- package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
- package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
- package/LICENSES/three-MIT.txt +23 -0
- package/LICENSES/video-use-MIT.txt +24 -0
- package/LICENSES/woven-sfx-CC0.txt +16 -0
- package/LICENSES/yunet-MIT.txt +21 -0
- package/README.md +2 -2
- package/bin/primocode.js +74 -9
- package/editor/LICENSE.md +11 -0
- package/editor/README.md +321 -0
- package/editor/SKILL.md +307 -0
- package/editor/THIRD_PARTY_NOTICES.md +228 -0
- package/editor/assets/baixar.json +591 -0
- package/editor/assets/fonts/anton/OFL.txt +93 -0
- package/editor/assets/fonts/caveat/OFL.txt +93 -0
- package/editor/assets/fonts/figtree/OFL.txt +93 -0
- package/editor/assets/fonts/fonts.json +10 -0
- package/editor/assets/fonts/montserrat/OFL.txt +93 -0
- package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
- package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
- package/editor/assets/models/YUNET_LICENSE.txt +21 -0
- package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
- package/editor/assets/sfx/licencas/cash.txt +76 -0
- package/editor/assets/sfx/licencas/charge.txt +31 -0
- package/editor/assets/sfx/licencas/click.txt +46 -0
- package/editor/assets/sfx/licencas/coin.txt +50 -0
- package/editor/assets/sfx/licencas/count.txt +47 -0
- package/editor/assets/sfx/licencas/data.txt +31 -0
- package/editor/assets/sfx/licencas/error.txt +32 -0
- package/editor/assets/sfx/licencas/flash.txt +31 -0
- package/editor/assets/sfx/licencas/gears.txt +82 -0
- package/editor/assets/sfx/licencas/keyboard.txt +48 -0
- package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
- package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
- package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
- package/editor/assets/sfx/licencas/notification.txt +54 -0
- package/editor/assets/sfx/licencas/pop.txt +38 -0
- package/editor/assets/sfx/licencas/rewind.txt +32 -0
- package/editor/assets/sfx/licencas/rise.txt +46 -0
- package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
- package/editor/assets/sfx/licencas/snap.txt +50 -0
- package/editor/assets/sfx/licencas/success.txt +40 -0
- package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
- package/editor/assets/sfx/licencas/tick.txt +14 -0
- package/editor/assets/sfx/licencas/whoosh.txt +47 -0
- package/editor/assets/sfx/licencas/wind.txt +31 -0
- package/editor/assets/sfx/sfx.json +832 -0
- package/editor/assets/sfx/sfx_sinteticos.json +317 -0
- package/editor/assets/sfx/sfx_woven.json +387 -0
- package/editor/assets/sfx/woven/LICENCA.md +16 -0
- package/editor/fichas/briefing-do-video.md +45 -0
- package/editor/fichas/conferencia-da-amostra.md +56 -0
- package/editor/fichas/matriz-de-ganchos.csv +3 -0
- package/editor/fichas/padrao.json +16 -0
- package/editor/fichas/planilha-de-cenas.csv +4 -0
- package/editor/fichas/planilha-de-versoes.csv +4 -0
- package/editor/fichas/plano-em-blocos.md +32 -0
- package/editor/fichas/registro-de-materiais.json +27 -0
- package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
- package/editor/helpers/bordas_precisas.py +272 -0
- package/editor/helpers/camera_viva.py +426 -0
- package/editor/helpers/captions.py +601 -0
- package/editor/helpers/common.py +308 -0
- package/editor/helpers/compose_final.py +3555 -0
- package/editor/helpers/conferir_corte.py +234 -0
- package/editor/helpers/conferir_dados.py +405 -0
- package/editor/helpers/contact_sheet.py +282 -0
- package/editor/helpers/corte_por_onda.py +433 -0
- package/editor/helpers/corte_seco.py +595 -0
- package/editor/helpers/denoise.py +971 -0
- package/editor/helpers/efeitos.py +408 -0
- package/editor/helpers/efeitos_gerar.py +159 -0
- package/editor/helpers/elevenlabs_api.py +442 -0
- package/editor/helpers/face_layout.py +570 -0
- package/editor/helpers/fetch_images.py +423 -0
- package/editor/helpers/ganho_por_trecho.py +214 -0
- package/editor/helpers/gen_image.py +312 -0
- package/editor/helpers/gen_music.py +316 -0
- package/editor/helpers/gen_sfx.py +148 -0
- package/editor/helpers/grade.py +307 -0
- package/editor/helpers/icloud.py +119 -0
- package/editor/helpers/make_sfx.py +940 -0
- package/editor/helpers/matte.py +1342 -0
- package/editor/helpers/medir_referencia.py +2232 -0
- package/editor/helpers/mixar_trilha.py +354 -0
- package/editor/helpers/montar_corte.py +295 -0
- package/editor/helpers/motion/__init__.py +13 -0
- package/editor/helpers/motion/engine.py +1915 -0
- package/editor/helpers/motion/registry.py +510 -0
- package/editor/helpers/motion/render.py +665 -0
- package/editor/helpers/motion/templates/barra_progresso.py +370 -0
- package/editor/helpers/motion/templates/carimbo.py +132 -0
- package/editor/helpers/motion/templates/checklist.py +271 -0
- package/editor/helpers/motion/templates/comentario_cta.py +380 -0
- package/editor/helpers/motion/templates/contador.py +222 -0
- package/editor/helpers/motion/templates/destaque.py +263 -0
- package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
- package/editor/helpers/motion/templates/grafico_barras.py +510 -0
- package/editor/helpers/motion/templates/grafico_linha.py +462 -0
- package/editor/helpers/motion/templates/notificacao.py +266 -0
- package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
- package/editor/helpers/motion/templates/passos.py +321 -0
- package/editor/helpers/motion/templates/riscado.py +298 -0
- package/editor/helpers/motion/templates/selo_garantia.py +150 -0
- package/editor/helpers/motion_render.py +241 -0
- package/editor/helpers/motion_sugerir.py +961 -0
- package/editor/helpers/noise_check.py +134 -0
- package/editor/helpers/pack_transcripts.py +223 -0
- package/editor/helpers/palavras_na_onda.py +176 -0
- package/editor/helpers/plano_sfx.py +839 -0
- package/editor/helpers/portao.py +427 -0
- package/editor/helpers/preview_server.py +1177 -0
- package/editor/helpers/rastrear_rosto.py +298 -0
- package/editor/helpers/render_cut.py +1256 -0
- package/editor/helpers/respiros.py +89 -0
- package/editor/helpers/sincronizar_cameras.py +571 -0
- package/editor/helpers/speech_regions.py +423 -0
- package/editor/helpers/style_catalog.json +231 -0
- package/editor/helpers/style_samples.py +246 -0
- package/editor/helpers/tela_verde.py +2113 -0
- package/editor/helpers/text_render.py +2059 -0
- package/editor/helpers/texto_na_tela.py +366 -0
- package/editor/helpers/timeline_view.py +292 -0
- package/editor/helpers/tira_de_quadros.py +113 -0
- package/editor/helpers/transcribe.py +734 -0
- package/editor/helpers/transcribe_batch.py +129 -0
- package/editor/helpers/trilhas.py +1146 -0
- package/editor/helpers/trilhas_humores.json +66 -0
- package/editor/helpers/valores.py +187 -0
- package/editor/helpers/verify_cut.py +820 -0
- package/editor/helpers/voice_levels.py +233 -0
- package/editor/helpers/voz_check.py +386 -0
- package/editor/helpers/voz_limpa.py +81 -0
- package/editor/helpers/watch_preview.py +372 -0
- package/editor/modelo-projeto/LEIA.md +402 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
- package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
- package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
- package/editor/modelo-projeto/assets/three/LICENSE +21 -0
- package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
- package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
- package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
- package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
- package/editor/modelo-projeto/build.py +4828 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
- package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
- package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
- package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
- package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
- package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
- package/editor/modelo-projeto/hyperframes.json +12 -0
- package/editor/modelo-projeto/package.json +11 -0
- package/editor/modelo-projeto/projeto.json +16 -0
- package/editor/preview/app.css +1009 -0
- package/editor/preview/app.js +2122 -0
- package/editor/preview/index.html +307 -0
- package/editor/pyproject.toml +23 -0
- package/editor/references/3d.md +236 -0
- package/editor/references/animacao-catalogo.md +672 -0
- package/editor/references/animacao.md +498 -0
- package/editor/references/comandos.md +92 -0
- package/editor/references/corte-seco-como-funciona.md +50 -0
- package/editor/references/corte-seco.md +88 -0
- package/editor/references/corte.md +290 -0
- package/editor/references/direcao-por-blocos.md +218 -0
- package/editor/references/enquadramento.md +654 -0
- package/editor/references/estilos-prontos.md +355 -0
- package/editor/references/estudio.md +100 -0
- package/editor/references/exemplo-eventos.json +67 -0
- package/editor/references/lettering.md +252 -0
- package/editor/references/modelos.md +494 -0
- package/editor/references/phase2.md +1007 -0
- package/editor/references/referencia-aplicar.md +203 -0
- package/editor/references/referencia-medir.md +375 -0
- package/editor/references/revisao.md +335 -0
- package/editor/references/som-e-trilha.md +146 -0
- package/editor/references/tabela-de-sons.md +130 -0
- package/editor/references/variacoes.md +234 -0
- package/editor/uv.lock +1141 -0
- package/lib/act.js +38 -8
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +7 -1
- package/lib/chamada-torta.js +81 -0
- package/lib/editor-pro.js +786 -0
- package/lib/entrega.js +58 -0
- package/lib/estudio/conferencia-visual.js +76 -0
- package/lib/estudio/diretor.js +6 -1
- package/lib/estudio/exportar.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +305 -0
- package/lib/estudio/index.js +12 -1
- package/lib/estudio/kit-ae/base.jsx +119 -13
- package/lib/estudio/kit-ae/cenas.jsx +47 -15
- package/lib/estudio/kit-ae/guarda.jsx +274 -0
- package/lib/estudio/kit-ae/primitivas.jsx +180 -24
- package/lib/estudio/maquina-diretor.js +70 -12
- package/lib/estudio/maquina-foto.js +177 -0
- package/lib/estudio/maquina-guarda.js +207 -0
- package/lib/estudio/maquina-layout.js +22 -1
- package/lib/estudio/maquina.js +64 -11
- package/lib/estudio/referencias.js +1 -1
- package/lib/estudio/visao/visao.swift +97 -0
- package/lib/estudio/visao.js +60 -0
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +285 -7
- package/lib/local/motor.js +5 -2
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +33 -2
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +19 -11
- package/lib/protecao.js +134 -0
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +23 -4
- package/lib/ui.js +2 -2
- package/package.json +3 -2
- package/skills/editar-meu-video/SKILL.md +6 -2
- package/skills/editar-video/SKILL.md +113 -0
- package/studio/pessoa.py +52 -0
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +500 -4
- package/voz/primo_voz/chat.html +1773 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
- package/voz/primo_voz/painel_dados.py +134 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Sound effect library by category, outside the skill (like the music library in trilhas.py).
|
|
2
|
+
|
|
3
|
+
macOS / Linux: ~/.editor-video/efeitos Windows: %USERPROFILE%\\.editor-video\\efeitos
|
|
4
|
+
(or the folder in EDITOR_VIDEO_EFEITOS)
|
|
5
|
+
|
|
6
|
+
The 24 built-in effects (assets/sfx) are one sound per name, so three pops in a row sound the same. The
|
|
7
|
+
library keeps many takes per category, each cut, cleaned and set to the kit level, with where it came from
|
|
8
|
+
and its license. In edit_data.json a sound is asked for by category ("transicao", "humor"...) and every event
|
|
9
|
+
gets a different take, or narrowed by the source name ("impacto:boom", "transicao:swipe", "humor:boing"), or by an
|
|
10
|
+
exact id ("humor-012"). compose_final resolves those names here.
|
|
11
|
+
|
|
12
|
+
Categories: transicao (whoosh, swish, passagem), impacto (batida, soco, explosão curta), revelacao (grave que
|
|
13
|
+
cresce, brilho mágico, clima antes de mostrar), interface (clique, toque, botão), tecnologia (IA, laser,
|
|
14
|
+
bipes, energia), sucesso (confirmação, subida), erro (negação, falha), humor (boing, pop de desenho, apito).
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
efeitos.py importar PASTA --origem kenney|sse|arquivos [--categoria C] [--licenca TEXTO] [--fonte URL]
|
|
18
|
+
cuts every file in PASTA (long reels split at the pauses), levels it and files it by category
|
|
19
|
+
efeitos.py listar [--categoria C] what the library has, per category
|
|
20
|
+
efeitos.py amostra --categoria C one mp3 with every take of C and a list with the time of each one
|
|
21
|
+
efeitos.py remover ID [ID ...] moves takes to _lixeira (restaurar brings them back)
|
|
22
|
+
efeitos.py restaurar ID [ID ...]
|
|
23
|
+
efeitos.py onde the library folder
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import hashlib
|
|
30
|
+
import json
|
|
31
|
+
import math
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import shutil
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
37
|
+
import unicodedata
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
|
|
42
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
43
|
+
import make_sfx # noqa: E402
|
|
44
|
+
|
|
45
|
+
SR = make_sfx.SR
|
|
46
|
+
LIBRARY_ENV = "EDITOR_VIDEO_EFEITOS"
|
|
47
|
+
INDEX = "efeitos.json"
|
|
48
|
+
UP_DB = 20 * math.log10(make_sfx.SFX_CALIBRATION) # what sfx.volume 1.0 adds in compose_final
|
|
49
|
+
|
|
50
|
+
# category -> (label, when to use, loudest 200 ms at sfx.volume 1.0 in LUFS, longest take in s)
|
|
51
|
+
CATEGORIES = {
|
|
52
|
+
"transicao": ("Transição", "whoosh e swish: corte, troca de assunto, animação ou imagem entrando", -10.0, 2.5),
|
|
53
|
+
"impacto": ("Impacto", "batida, soco, explosão curta: frase forte, número, palavra que bate na tela", -10.0, 3.0),
|
|
54
|
+
"revelacao": ("Revelação", "grave que cresce, brilho mágico: antes ou no momento de mostrar algo", -12.0, 6.0),
|
|
55
|
+
"interface": ("Interface", "clique, toque, botão, rolagem: item de lista, tela, celular", -14.0, 1.2),
|
|
56
|
+
"tecnologia": ("Tecnologia", "IA, laser, bipes, energia, dados: sistema trabalhando, robô, app", -13.0, 3.0),
|
|
57
|
+
"sucesso": ("Sucesso", "confirmação, subida, nível acima: deu certo, check, conquista", -13.0, 2.5),
|
|
58
|
+
"erro": ("Erro", "negação, falha, buzina: o que não fazer, erro comum", -13.0, 2.0),
|
|
59
|
+
"humor": ("Humor", "boing, pop de desenho, apito, soco de desenho: quebra de padrão, piada", -11.0, 3.0),
|
|
60
|
+
}
|
|
61
|
+
LONG_CAP = -20.0 # loudest 3 s window at sfx.volume 1.0 (the kit's ceiling for long sounds)
|
|
62
|
+
PEAK_MAX_DB = -1.0
|
|
63
|
+
MAX_PER_GROUP = 8 # takes kept per source name (ui-audio has 38 switches)
|
|
64
|
+
|
|
65
|
+
# (origin, regex on "folder/file") -> category or None (left out), first match wins
|
|
66
|
+
RULES = [
|
|
67
|
+
("kenney", r"footstep|spaceengine|thruster|enginecircular|slime|spacetrash|preview", None),
|
|
68
|
+
("kenney", r"confirmation|powerup|phaserup|highup|threetone(up)?|zapthreetoneup", "sucesso"),
|
|
69
|
+
("kenney", r"interface.*error|lowdown|zapthreetonedown|phaserdown|highdown", "erro"),
|
|
70
|
+
("kenney", r"explosion|impactmetal|impactpunch|impactplate|impactwood|impacttin|impactsoft|impactplank|"
|
|
71
|
+
r"impactmining|impactglass|impactbell|impactgeneric", "impacto"),
|
|
72
|
+
("kenney", r"door(open|close)", "transicao"),
|
|
73
|
+
("kenney", r"laser|forcefield|computernoise|zap|phase|tone|pepsound|glitch|lowrandom|lowthree", "tecnologia"),
|
|
74
|
+
("kenney", r"scratch", "humor"),
|
|
75
|
+
("kenney", r".", "interface"),
|
|
76
|
+
("sse", r"swooshes|fireball", "transicao"),
|
|
77
|
+
("sse", r"rumble|magic", "revelacao"),
|
|
78
|
+
("sse", r"cartoon|whistles", "humor"),
|
|
79
|
+
("sse", r"explosions", "impacto"),
|
|
80
|
+
("sse", r".", "tecnologia"),
|
|
81
|
+
("sintetico", r"whoosh|swipe", "transicao"),
|
|
82
|
+
("sintetico", r"riser|swell", "revelacao"),
|
|
83
|
+
("sintetico", r"boom808", "humor"),
|
|
84
|
+
("sintetico", r"boom", "impacto"),
|
|
85
|
+
("sintetico", r"glitch", "tecnologia"),
|
|
86
|
+
]
|
|
87
|
+
LICENSES = {
|
|
88
|
+
"kenney": ("CC0 1.0 (domínio público), Kenney (www.kenney.nl)", "https://kenney.nl/assets/category:Audio"),
|
|
89
|
+
"sse": ("CC0 1.0 (domínio público), SSE Library / USC Cinema no Internet Archive",
|
|
90
|
+
"https://archive.org/details/SSE_Library_SWOOSHES"),
|
|
91
|
+
"sintetico": ("gerado pelo editor-video (helpers/efeitos_gerar.py), código próprio", "helpers/efeitos_gerar.py"),
|
|
92
|
+
}
|
|
93
|
+
AUDIO_EXT = {".wav", ".ogg", ".mp3", ".flac", ".aif", ".aiff", ".m4a"}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ------------------------------------------------------------------ library
|
|
97
|
+
|
|
98
|
+
def library_dir() -> Path:
|
|
99
|
+
raw = (os.environ.get(LIBRARY_ENV) or "").strip()
|
|
100
|
+
return Path(raw).expanduser() if raw else Path.home() / ".editor-video" / "efeitos"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def load_index(lib: Path | None = None) -> dict:
|
|
104
|
+
lib = lib or library_dir()
|
|
105
|
+
try:
|
|
106
|
+
data = json.loads((lib / INDEX).read_text(encoding="utf-8"))
|
|
107
|
+
return data if isinstance(data, dict) and isinstance(data.get("efeitos"), dict) else {"efeitos": {}}
|
|
108
|
+
except (OSError, ValueError):
|
|
109
|
+
return {"efeitos": {}}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def save_index(lib: Path, index: dict) -> None:
|
|
113
|
+
lib.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
tmp = lib / (INDEX + ".tmp")
|
|
115
|
+
tmp.write_text(json.dumps(index, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
|
|
116
|
+
tmp.replace(lib / INDEX)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def slug(text: str) -> str:
|
|
120
|
+
t = unicodedata.normalize("NFD", str(text).lower())
|
|
121
|
+
t = "".join(c for c in t if unicodedata.category(c) != "Mn")
|
|
122
|
+
return re.sub(r"[^a-z0-9]+", "-", t).strip("-")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def category_of(name: str) -> str | None:
|
|
126
|
+
s = slug(name)
|
|
127
|
+
for cid, (label, _, _, _) in CATEGORIES.items():
|
|
128
|
+
if s in (cid, slug(label)):
|
|
129
|
+
return cid
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def takes(category: str, lib: Path | None = None, index: dict | None = None) -> list[tuple[str, dict]]:
|
|
134
|
+
lib = lib or library_dir()
|
|
135
|
+
index = index or load_index(lib)
|
|
136
|
+
return sorted((k, v) for k, v in index["efeitos"].items()
|
|
137
|
+
if v.get("categoria") == category and (lib / v["arquivo"]).is_file())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def resolve(names, lib: Path | None = None, seed: str = "") -> dict:
|
|
141
|
+
"""{name: {"path", "anchor_s", "id", "takes": [...]}} for names that are a library category or take id.
|
|
142
|
+
|
|
143
|
+
A category gets every take in a fixed shuffled order (seeded by `seed`, the edit folder): the caller hands
|
|
144
|
+
them out one per event, so a category used 5 times plays 5 different takes."""
|
|
145
|
+
lib = lib or library_dir()
|
|
146
|
+
index = load_index(lib)
|
|
147
|
+
out = {}
|
|
148
|
+
for name in names:
|
|
149
|
+
base, _, term = str(name).partition(":") # "impacto:boom" = impacto takes whose name has "boom"
|
|
150
|
+
cat = category_of(base)
|
|
151
|
+
if cat:
|
|
152
|
+
ts = takes(cat, lib, index)
|
|
153
|
+
if term:
|
|
154
|
+
ts = [kv for kv in ts if slug(term).replace("-", "") in slug(kv[1].get("nome", "")).replace("-", "")]
|
|
155
|
+
if not ts:
|
|
156
|
+
continue
|
|
157
|
+
order = sorted(ts, key=lambda kv: hashlib.sha1(f"{seed}|{kv[0]}".encode()).hexdigest())
|
|
158
|
+
out[name] = {"id": cat, "takes": [{"path": lib / v["arquivo"], "anchor_s": float(v.get("anchor_s", 0)),
|
|
159
|
+
"id": k} for k, v in order]}
|
|
160
|
+
continue
|
|
161
|
+
v = index["efeitos"].get(slug(name))
|
|
162
|
+
if v and (lib / v["arquivo"]).is_file():
|
|
163
|
+
t = {"path": lib / v["arquivo"], "anchor_s": float(v.get("anchor_s", 0)), "id": slug(name)}
|
|
164
|
+
out[name] = {"id": slug(name), "takes": [t]}
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ------------------------------------------------------------------ cutting and leveling
|
|
169
|
+
|
|
170
|
+
def decode(path: Path) -> np.ndarray:
|
|
171
|
+
"""(2, n) float at 48 kHz."""
|
|
172
|
+
raw = subprocess.run(["ffmpeg", "-v", "error", "-i", str(path), "-ac", "2", "-ar", str(SR), "-f", "f32le", "-"],
|
|
173
|
+
capture_output=True, check=True).stdout
|
|
174
|
+
x = np.frombuffer(raw, dtype="<f4").astype(np.float64)
|
|
175
|
+
return x.reshape(-1, 2).T
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def env_db(x: np.ndarray, hop: int = SR // 100) -> np.ndarray:
|
|
179
|
+
m = x.mean(axis=0)
|
|
180
|
+
n = len(m) // hop
|
|
181
|
+
if n == 0:
|
|
182
|
+
return np.full(1, -120.0)
|
|
183
|
+
f = m[: n * hop].reshape(n, hop)
|
|
184
|
+
return 10 * np.log10((f * f).mean(axis=1) + 1e-12)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def pieces(x: np.ndarray, max_len: float, split: bool) -> list[np.ndarray]:
|
|
188
|
+
"""The sounds in x: one trimmed piece, or (split) every sound between pauses of 0.25 s or more."""
|
|
189
|
+
hop = SR // 100
|
|
190
|
+
e = env_db(x, hop)
|
|
191
|
+
top = float(np.percentile(e, 99))
|
|
192
|
+
thr = max(float(np.percentile(e, 5)) + 18.0, top - 45.0)
|
|
193
|
+
loud = e >= thr
|
|
194
|
+
spans, k = [], 0
|
|
195
|
+
gap = 25
|
|
196
|
+
while k < len(loud):
|
|
197
|
+
if not loud[k]:
|
|
198
|
+
k += 1
|
|
199
|
+
continue
|
|
200
|
+
a, quiet = k, 0
|
|
201
|
+
while k < len(loud) and (quiet < gap or not split):
|
|
202
|
+
quiet = 0 if loud[k] else quiet + 1
|
|
203
|
+
k += 1
|
|
204
|
+
if not split and k >= len(loud):
|
|
205
|
+
break
|
|
206
|
+
b = k - quiet if split else int(np.nonzero(loud)[0][-1]) + 1
|
|
207
|
+
spans.append((a, b))
|
|
208
|
+
if not split:
|
|
209
|
+
break
|
|
210
|
+
out = []
|
|
211
|
+
for a, b in spans:
|
|
212
|
+
if (b - a) * hop < 0.06 * SR:
|
|
213
|
+
continue
|
|
214
|
+
s0 = max(0, a * hop - int(0.02 * SR))
|
|
215
|
+
s1 = min(x.shape[1], b * hop + int(0.15 * SR))
|
|
216
|
+
s1 = min(s1, s0 + int(max_len * SR))
|
|
217
|
+
piece = x[:, s0:s1].copy()
|
|
218
|
+
fade = min(int(0.03 * SR), piece.shape[1] // 4)
|
|
219
|
+
if fade > 0:
|
|
220
|
+
piece[:, -fade:] *= np.linspace(1, 0, fade)
|
|
221
|
+
piece[:, : int(0.003 * SR)] *= np.linspace(0, 1, int(0.003 * SR))
|
|
222
|
+
out.append(piece)
|
|
223
|
+
return out
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def level(piece: np.ndarray, target_200: float) -> tuple[np.ndarray, dict] | None:
|
|
227
|
+
"""Gain so the loudest 200 ms sits at target_200 at sfx.volume 1.0, the 3 s window never above LONG_CAP,
|
|
228
|
+
the peak never above PEAK_MAX_DB. None for a piece that is almost silent."""
|
|
229
|
+
m = make_sfx.measure(piece)
|
|
230
|
+
if m["loudness_200ms_lufs"] < -60:
|
|
231
|
+
return None
|
|
232
|
+
gain = (target_200 - UP_DB) - m["loudness_200ms_lufs"]
|
|
233
|
+
gain = min(gain, (LONG_CAP - UP_DB) - m["short_term_max_lufs"], PEAK_MAX_DB - m["peak_dbfs"])
|
|
234
|
+
out = piece * (10 ** (gain / 20))
|
|
235
|
+
return out, make_sfx.measure(out)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def write_wav(path: Path, x: np.ndarray) -> None:
|
|
239
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
make_sfx.write_wav(path, x)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def classify(origin: str, rel: str) -> str | None:
|
|
244
|
+
key = slug(rel).replace("-", "")
|
|
245
|
+
for org, rx, cat in RULES:
|
|
246
|
+
if org == origin and re.search(rx, key, re.I):
|
|
247
|
+
return cat
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def group_name(rel: str) -> str:
|
|
252
|
+
stem = Path(rel).stem
|
|
253
|
+
stem = re.sub(r"_CH?S_USC$", "", stem)
|
|
254
|
+
return re.sub(r"[_\-]?\d+$", "", stem) or stem
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def importar(folder: Path, origin: str, categoria: str | None, licenca: str | None, fonte: str | None,
|
|
258
|
+
lib: Path, dry: bool = False) -> dict:
|
|
259
|
+
index = load_index(lib)
|
|
260
|
+
have = {v.get("origem_arquivo"): k for k, v in index["efeitos"].items()}
|
|
261
|
+
counts: dict[str, int] = {}
|
|
262
|
+
groups: dict[str, int] = {}
|
|
263
|
+
files = sorted(p for p in folder.rglob("*") if p.suffix.lower() in AUDIO_EXT and "_lixeira" not in p.parts)
|
|
264
|
+
lic, src = LICENSES.get(origin, (licenca or "", fonte or ""))
|
|
265
|
+
if licenca:
|
|
266
|
+
lic = licenca
|
|
267
|
+
if fonte:
|
|
268
|
+
src = fonte
|
|
269
|
+
skipped = 0
|
|
270
|
+
for p in files:
|
|
271
|
+
rel = str(p.relative_to(folder))
|
|
272
|
+
cat = categoria or classify(origin, rel)
|
|
273
|
+
if not cat:
|
|
274
|
+
skipped += 1
|
|
275
|
+
continue
|
|
276
|
+
label, _, target, max_len = CATEGORIES[cat]
|
|
277
|
+
g = f"{cat}|{group_name(rel)}"
|
|
278
|
+
try:
|
|
279
|
+
x = decode(p)
|
|
280
|
+
except subprocess.CalledProcessError:
|
|
281
|
+
print(f"não abriu: {rel}")
|
|
282
|
+
continue
|
|
283
|
+
long_reel = x.shape[1] > 6 * SR
|
|
284
|
+
for j, piece in enumerate(pieces(x, max_len, split=long_reel)):
|
|
285
|
+
if groups.get(g, 0) >= MAX_PER_GROUP * (3 if long_reel else 1):
|
|
286
|
+
break
|
|
287
|
+
tag = f"{rel}#{j}"
|
|
288
|
+
if tag in have:
|
|
289
|
+
continue
|
|
290
|
+
got = level(piece, target)
|
|
291
|
+
if got is None:
|
|
292
|
+
continue
|
|
293
|
+
y, m = got
|
|
294
|
+
n = 1 + max([int(k.rsplit("-", 1)[1]) for k in index["efeitos"] if k.startswith(cat + "-")] or [0])
|
|
295
|
+
eid = f"{cat}-{n:03d}"
|
|
296
|
+
if not dry:
|
|
297
|
+
write_wav(lib / cat / f"{eid}.wav", y)
|
|
298
|
+
index["efeitos"][eid] = {
|
|
299
|
+
"categoria": cat, "arquivo": f"{cat}/{eid}.wav", "nome": group_name(rel),
|
|
300
|
+
"anchor_s": m["anchor_s"], "duracao_s": m["duration_s"],
|
|
301
|
+
"loudness_200ms_lufs": m["loudness_200ms_lufs"], "short_term_max_lufs": m["short_term_max_lufs"],
|
|
302
|
+
"origem": origin, "origem_arquivo": tag, "licenca": lic, "fonte": src}
|
|
303
|
+
have[tag] = eid
|
|
304
|
+
groups[g] = groups.get(g, 0) + 1
|
|
305
|
+
counts[cat] = counts.get(cat, 0) + 1
|
|
306
|
+
if not dry:
|
|
307
|
+
save_index(lib, index)
|
|
308
|
+
return {"novos": counts, "fora": skipped}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ------------------------------------------------------------------ commands
|
|
312
|
+
|
|
313
|
+
def cmd_listar(lib: Path, categoria: str | None) -> None:
|
|
314
|
+
index = load_index(lib)
|
|
315
|
+
for cid, (label, when, _, _) in CATEGORIES.items():
|
|
316
|
+
if categoria and cid != category_of(categoria):
|
|
317
|
+
continue
|
|
318
|
+
ts = takes(cid, lib, index)
|
|
319
|
+
print(f"{cid:<11} {len(ts):>4} sons · {label}: {when}")
|
|
320
|
+
if categoria:
|
|
321
|
+
for k, v in ts:
|
|
322
|
+
print(f" {k} {v['duracao_s']:.2f} s {v['nome']} ({v['origem']})")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def cmd_amostra(lib: Path, categoria: str, out: Path | None) -> Path:
|
|
326
|
+
cat = category_of(categoria)
|
|
327
|
+
if not cat:
|
|
328
|
+
raise SystemExit(f"categoria '{categoria}' não existe ({', '.join(CATEGORIES)})")
|
|
329
|
+
ts = takes(cat, lib)
|
|
330
|
+
if not ts:
|
|
331
|
+
raise SystemExit(f"a categoria {cat} está vazia")
|
|
332
|
+
gap = np.zeros((2, int(0.45 * SR)))
|
|
333
|
+
parts, lines, t = [], [], 0.0
|
|
334
|
+
for k, v in ts:
|
|
335
|
+
x = make_sfx.read_audio(lib / v["arquivo"])
|
|
336
|
+
x = x if x.shape[0] == 2 else np.vstack([x[0], x[0]])
|
|
337
|
+
lines.append(f"{int(t // 60):02d}:{t % 60:05.2f} {k} {v['nome']}")
|
|
338
|
+
parts += [x, gap]
|
|
339
|
+
t += x.shape[1] / SR + gap.shape[1] / SR
|
|
340
|
+
out = out or lib / "_amostras" / f"amostra_{cat}.mp3"
|
|
341
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
342
|
+
wav = out.with_suffix(".wav")
|
|
343
|
+
write_wav(wav, np.hstack(parts))
|
|
344
|
+
subprocess.run(["ffmpeg", "-v", "error", "-y", "-i", str(wav), "-b:a", "192k", str(out)], check=True)
|
|
345
|
+
wav.unlink()
|
|
346
|
+
out.with_suffix(".txt").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
347
|
+
print(f"{len(ts)} sons em {out} (tempo de cada um em {out.with_suffix('.txt').name})")
|
|
348
|
+
return out
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def cmd_mover(lib: Path, ids: list[str], back: bool) -> None:
|
|
352
|
+
index = load_index(lib)
|
|
353
|
+
trash = lib / "_lixeira"
|
|
354
|
+
for eid in ids:
|
|
355
|
+
v = index["efeitos"].get(slug(eid))
|
|
356
|
+
if not v:
|
|
357
|
+
print(f"{eid}: não existe")
|
|
358
|
+
continue
|
|
359
|
+
src, dst = lib / v["arquivo"], trash / v["arquivo"]
|
|
360
|
+
if back:
|
|
361
|
+
src, dst = dst, src
|
|
362
|
+
if src.is_file():
|
|
363
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
364
|
+
shutil.move(str(src), str(dst))
|
|
365
|
+
print(f"{eid}: {'de volta' if back else 'na lixeira'}")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def main() -> int:
|
|
369
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
370
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
371
|
+
im = sub.add_parser("importar")
|
|
372
|
+
im.add_argument("pasta", type=Path)
|
|
373
|
+
im.add_argument("--origem", default="arquivos")
|
|
374
|
+
im.add_argument("--categoria", choices=list(CATEGORIES))
|
|
375
|
+
im.add_argument("--licenca")
|
|
376
|
+
im.add_argument("--fonte")
|
|
377
|
+
im.add_argument("--dry-run", action="store_true")
|
|
378
|
+
ls = sub.add_parser("listar")
|
|
379
|
+
ls.add_argument("--categoria")
|
|
380
|
+
am = sub.add_parser("amostra")
|
|
381
|
+
am.add_argument("--categoria", required=True)
|
|
382
|
+
am.add_argument("-o", type=Path)
|
|
383
|
+
for nm in ("remover", "restaurar"):
|
|
384
|
+
sub.add_parser(nm).add_argument("ids", nargs="+")
|
|
385
|
+
sub.add_parser("onde")
|
|
386
|
+
a = ap.parse_args()
|
|
387
|
+
lib = library_dir()
|
|
388
|
+
if a.cmd == "importar":
|
|
389
|
+
if a.origem == "arquivos" and not a.categoria:
|
|
390
|
+
raise SystemExit("arquivos seus: diga a --categoria")
|
|
391
|
+
if a.origem not in LICENSES and not a.licenca:
|
|
392
|
+
raise SystemExit("diga a --licenca (só entra som com direito de uso comercial)")
|
|
393
|
+
r = importar(a.pasta, a.origem, a.categoria, a.licenca, a.fonte, lib, a.dry_run)
|
|
394
|
+
print("novos: " + (", ".join(f"{k} {v}" for k, v in sorted(r["novos"].items())) or "nenhum") +
|
|
395
|
+
f" · fora (não servem): {r['fora']} arquivos")
|
|
396
|
+
elif a.cmd == "listar":
|
|
397
|
+
cmd_listar(lib, a.categoria)
|
|
398
|
+
elif a.cmd == "amostra":
|
|
399
|
+
cmd_amostra(lib, a.categoria, a.o)
|
|
400
|
+
elif a.cmd in ("remover", "restaurar"):
|
|
401
|
+
cmd_mover(lib, a.ids, a.cmd == "restaurar")
|
|
402
|
+
elif a.cmd == "onde":
|
|
403
|
+
print(lib)
|
|
404
|
+
return 0
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == "__main__":
|
|
408
|
+
sys.exit(main())
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Procedural takes for the sound effect library (efeitos.py): variations the CC0 packs have few of.
|
|
2
|
+
|
|
3
|
+
Every sound is made here with numpy (make_sfx building blocks), from a fixed seed: the same command gives the
|
|
4
|
+
same files. Own code, so the files can go anywhere (package included). Types and where they are filed:
|
|
5
|
+
|
|
6
|
+
whoosh (transicao) band-passed noise sweeping up or down, panning across, 0.35 to 1.2 s
|
|
7
|
+
swipe (transicao) short fast sweep, 0.16 to 0.3 s
|
|
8
|
+
riser (revelacao) noise and a tone climbing to a stop, 1.2 to 3 s (the strong point is the end)
|
|
9
|
+
swell (revelacao) reverse-cymbal-like swell that stops dead, 0.9 to 2 s
|
|
10
|
+
boom (impacto) sub drop with a noise hit, 0.8 to 1.6 s
|
|
11
|
+
boom808 (humor) saturated 808 thump with a room tail (the "boom" of memes, generic)
|
|
12
|
+
glitch (tecnologia) stuttered digital slices, 0.3 to 0.7 s
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
uv run python helpers/efeitos_gerar.py PASTA [--por-tipo 8]
|
|
16
|
+
uv run python helpers/efeitos.py importar PASTA --origem sintetico
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
28
|
+
import make_sfx as M # noqa: E402
|
|
29
|
+
|
|
30
|
+
SR = M.SR
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _t(d: float) -> np.ndarray:
|
|
34
|
+
return np.arange(int(d * SR)) / SR
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _pan(mono: np.ndarray, start: float, end: float) -> np.ndarray:
|
|
38
|
+
p = np.linspace(start, end, len(mono)) # -1 left .. 1 right, equal power
|
|
39
|
+
a = (p + 1) * np.pi / 4
|
|
40
|
+
return np.stack([mono * np.cos(a), mono * np.sin(a)])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def whoosh(rng) -> np.ndarray:
|
|
44
|
+
d = rng.uniform(0.35, 1.2)
|
|
45
|
+
t = _t(d)
|
|
46
|
+
up = rng.random() < 0.6
|
|
47
|
+
f0, f1 = (rng.uniform(250, 600), rng.uniform(2500, 6000)) if up else (rng.uniform(2500, 5000), rng.uniform(250, 600))
|
|
48
|
+
peak = rng.uniform(0.45, 0.7)
|
|
49
|
+
cut = f0 * (f1 / f0) ** (t / d)
|
|
50
|
+
x = M._svf_bandpass(rng.standard_normal(len(t)), cut, rng.uniform(1.2, 3.0))
|
|
51
|
+
env = np.where(t < peak * d, (t / (peak * d)) ** 2.2, np.exp(-(t - peak * d) / (0.18 * d)))
|
|
52
|
+
body = M._lp(rng.standard_normal(len(t)), 300.0) * 0.35
|
|
53
|
+
mono = M._fade_tail(M._unit(x) * env + M._unit(body) * env * 0.4, 30)
|
|
54
|
+
s = rng.choice([-1, 1])
|
|
55
|
+
return _pan(mono, -0.7 * s, 0.7 * s)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def swipe(rng) -> np.ndarray:
|
|
59
|
+
d = rng.uniform(0.16, 0.3)
|
|
60
|
+
t = _t(d)
|
|
61
|
+
cut = rng.uniform(1500, 3000) * (rng.uniform(3, 5)) ** (t / d)
|
|
62
|
+
x = M._svf_bandpass(rng.standard_normal(len(t)), cut, rng.uniform(2, 4))
|
|
63
|
+
env = np.sin(np.pi * t / d) ** 1.5
|
|
64
|
+
s = rng.choice([-1, 1])
|
|
65
|
+
return _pan(M._fade_tail(M._unit(x) * env, 10), -0.8 * s, 0.8 * s)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def riser(rng) -> np.ndarray:
|
|
69
|
+
d = rng.uniform(1.2, 3.0)
|
|
70
|
+
t = _t(d)
|
|
71
|
+
cut = 200 * (rng.uniform(25, 45)) ** (t / d)
|
|
72
|
+
noise = M._unit(M._svf_bandpass(rng.standard_normal(len(t)), cut, 1.5))
|
|
73
|
+
f = rng.uniform(120, 220) * (rng.uniform(4, 7)) ** ((t / d) ** 1.4)
|
|
74
|
+
tone = np.sign(np.sin(M._phase(f))) * 0.25 + np.sin(M._phase(f * 1.005)) * 0.5
|
|
75
|
+
tone = M._lp(tone, 3000.0)
|
|
76
|
+
env = (t / d) ** rng.uniform(1.8, 3.0)
|
|
77
|
+
mono = (noise * 0.8 + M._unit(tone) * 0.45) * env
|
|
78
|
+
mono[-int(0.01 * SR):] *= np.linspace(1, 0, int(0.01 * SR))
|
|
79
|
+
return _pan(mono, 0, 0) * 0.9 + np.stack([np.roll(mono, 240), mono]) * 0.1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def swell(rng) -> np.ndarray:
|
|
83
|
+
d = rng.uniform(0.9, 2.0)
|
|
84
|
+
t = _t(d)
|
|
85
|
+
x = M._bp(rng.standard_normal(len(t)), rng.uniform(2500, 4000), rng.uniform(9000, 14000))
|
|
86
|
+
env = np.exp((t - d) / (rng.uniform(0.18, 0.35) * d))
|
|
87
|
+
mono = M._unit(x) * env
|
|
88
|
+
mono[-int(0.008 * SR):] *= np.linspace(1, 0, int(0.008 * SR))
|
|
89
|
+
return np.stack([mono, np.roll(mono, 180)])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def boom(rng) -> np.ndarray:
|
|
93
|
+
d = rng.uniform(0.8, 1.6)
|
|
94
|
+
t = _t(d)
|
|
95
|
+
lo = rng.uniform(30, 42)
|
|
96
|
+
f = lo + (rng.uniform(90, 140) - lo) * np.exp(-t / rng.uniform(0.05, 0.12))
|
|
97
|
+
body = np.tanh(rng.uniform(1.5, 3.0) * np.sin(M._phase(f))) * np.exp(-t / (0.35 * d))
|
|
98
|
+
hit = M._lp(rng.standard_normal(len(t)), rng.uniform(1500, 3500)) * np.exp(-t / 0.05)
|
|
99
|
+
mono = M._fade_tail(M._fade_in(M._unit(body) + M._unit(hit) * 0.35, 0.5), 60)
|
|
100
|
+
return np.stack([mono, mono])
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def boom808(rng) -> np.ndarray:
|
|
104
|
+
d = rng.uniform(1.1, 1.6)
|
|
105
|
+
t = _t(d)
|
|
106
|
+
f = rng.uniform(45, 60) + rng.uniform(110, 160) * np.exp(-t / 0.04)
|
|
107
|
+
dry = np.tanh(rng.uniform(3.0, 5.0) * np.sin(M._phase(f))) * np.exp(-t / rng.uniform(0.35, 0.55))
|
|
108
|
+
wet = np.zeros_like(dry)
|
|
109
|
+
for k, g in ((0.031, 0.5), (0.047, 0.4), (0.071, 0.3), (0.113, 0.22), (0.17, 0.15)):
|
|
110
|
+
n = int(k * SR)
|
|
111
|
+
wet[n:] += dry[:-n] * g
|
|
112
|
+
mono = M._fade_tail(M._fade_in(M._unit(dry + M._lp(wet, 900.0)), 0.5), 80)
|
|
113
|
+
return np.stack([mono, np.roll(mono, 90)])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def glitch(rng) -> np.ndarray:
|
|
117
|
+
d = rng.uniform(0.3, 0.7)
|
|
118
|
+
n = int(d * SR)
|
|
119
|
+
out = np.zeros(n)
|
|
120
|
+
k = 0
|
|
121
|
+
while k < n:
|
|
122
|
+
L = int(rng.uniform(0.012, 0.06) * SR)
|
|
123
|
+
kind = rng.integers(3)
|
|
124
|
+
tt = np.arange(L) / SR
|
|
125
|
+
if kind == 0:
|
|
126
|
+
seg = np.sign(np.sin(2 * np.pi * rng.uniform(200, 2400) * tt))
|
|
127
|
+
elif kind == 1:
|
|
128
|
+
seg = rng.standard_normal(L)
|
|
129
|
+
else:
|
|
130
|
+
seg = np.sin(2 * np.pi * rng.uniform(800, 5000) * tt)
|
|
131
|
+
seg *= rng.uniform(0.3, 1.0) * (rng.random() > 0.15)
|
|
132
|
+
out[k:k + L] = seg[: max(0, min(L, n - k))]
|
|
133
|
+
k += L
|
|
134
|
+
out = M._fade_tail(M._lp(out, 7000.0), 15)
|
|
135
|
+
return _pan(M._unit(out), rng.uniform(-0.5, 0), rng.uniform(0, 0.5))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
TYPES = {"whoosh": whoosh, "swipe": swipe, "riser": riser, "swell": swell, "boom": boom, "boom808": boom808,
|
|
139
|
+
"glitch": glitch}
|
|
140
|
+
SEEDS = {name: 7000 + 100 * i for i, name in enumerate(TYPES)}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main() -> int:
|
|
144
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
145
|
+
ap.add_argument("pasta", type=Path)
|
|
146
|
+
ap.add_argument("--por-tipo", type=int, default=8)
|
|
147
|
+
a = ap.parse_args()
|
|
148
|
+
a.pasta.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
for name, fn in TYPES.items():
|
|
150
|
+
for i in range(a.por_tipo):
|
|
151
|
+
rng = np.random.default_rng(SEEDS[name] + i)
|
|
152
|
+
x = M._normalize(fn(rng), -3.0)
|
|
153
|
+
M.write_wav(a.pasta / f"{name}_{i + 1:02d}.wav", x)
|
|
154
|
+
print(f"{len(TYPES) * a.por_tipo} sons em {a.pasta}")
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
sys.exit(main())
|