From 402fb6004b36144dac9b2568147b9f0d81f58ee0 Mon Sep 17 00:00:00 2001 From: nextestudios <47460003+nextestudios@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:05:54 -0300 Subject: [PATCH] feat: use the Console Mode sounds for the controller cues (#287) Port of UiSoundSynth (AGPL-3.0, same team): soft sine+octave blips built in code; mark is a fourth in the same style. Removes the Kenney CC0 files and the converter. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.en-US.md | 2 + CHANGELOG.md | 2 + THIRD_PARTY_NOTICES.md | 13 +-- assets/sounds/LICENSE-Kenney-CC0.txt | 22 ----- assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt | 21 ---- assets/sounds/back.wav | Bin 4850 -> 0 bytes assets/sounds/confirm.wav | Bin 17996 -> 0 bytes assets/sounds/move.wav | Bin 7100 -> 0 bytes assets/sounds/select.wav | Bin 7604 -> 0 bytes docs/GUIDE.md | 2 +- docs/GUIDE.pt-BR.md | 2 +- src/ControlFS.Core/Audio/SoundBank.cs | 91 ++++++++++-------- src/ControlFS.Core/ControlFS.Core.csproj | 4 - .../Shell/WindowsSoundPlayer.cs | 2 +- .../Core/ControllerSoundsTests.cs | 14 +-- tools/make-sounds.py | 65 ------------- 16 files changed, 72 insertions(+), 168 deletions(-) delete mode 100644 assets/sounds/LICENSE-Kenney-CC0.txt delete mode 100644 assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt delete mode 100644 assets/sounds/back.wav delete mode 100644 assets/sounds/confirm.wav delete mode 100644 assets/sounds/move.wav delete mode 100644 assets/sounds/select.wav delete mode 100644 tools/make-sounds.py diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 03d03d8b..1224d286 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -3,6 +3,8 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Before publishing a version, add a `## [VERSION]` section to **both** files: the release workflow uses the section matching the tag and fails if either is missing. ## [Unreleased] +### Changes +- **Controller sounds: the Console Mode ones** (#287): the sounds are now the same soft console blips as Console Mode (another team app, AGPL-3.0, the same licence): a soft sine with a quiet octave, fast attack and decay, no audio files (built in code). Move, confirm (rising) and back are Console Mode's; mark is a fourth blip in the same style. **Médio** (medium) is Console Mode's original level, **alto** (high) twice that and **baixo** (low) half. The Kenney CC0 files from 0.16 are gone. ## [0.16.0-alpha.1] ### Changes diff --git a/CHANGELOG.md b/CHANGELOG.md index 66952003..7ca46b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CHANGELOG.en-US.md`. Antes de publicar uma versão, adicione uma seção `## [VERSÃO]` **nos dois arquivos**. O workflow de release usa a seção da tag e falha se faltar alguma. ## [Unreleased] +### Mudanças +- **Sons do controle: os do Console Mode** (#287): os sons passam a ser os mesmos toques macios de console do Console Mode (outro app da equipe, AGPL-3.0, a mesma licença): uma senoide suave com uma oitava baixinha, ataque rápido e queda, sem arquivo de áudio (montados em código). Mover, confirmar (sobe) e voltar são os do Console Mode; marcar é um quarto toque no mesmo estilo. O volume **médio** é o nível original do Console Mode, **alto** o dobro e **baixo** a metade. Os arquivos CC0 de Kenney da 0.16 saíram. ## [0.16.0-alpha.1] ### Mudanças diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index d6b28e8e..f88fe01d 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -101,13 +101,10 @@ AGPL-3.0-only deste código e não podem ser reutilizadas sem permissão dos don Sons do controle ------------------------------------------------------------------------------ -Os quatro toques de mover, marcar, confirmar e voltar (assets/sounds/*.wav, embutidos no aplicativo) vêm de dois pacotes de -Kenney (https://kenney.nl), ambos Creative Commons Zero (CC0 1.0, domínio público): uso pessoal, educacional e comercial e -redistribuição livres, sem exigir crédito (o crédito a Kenney é voluntário e está aqui). Do pacote "UI Audio": rollover5 (mover). -Do pacote "Interface Sounds": glass_002 (marcar), maximize_009 (confirmar) e back_002 (voltar). Foram aparados, suavizados com -um filtro passa-baixa, com fades e mesma energia por tools/make-sounds.py. As licenças originais estão em -assets/sounds/LICENSE-Kenney-CC0.txt e LICENSE-Kenney-UI-Audio-CC0.txt. Por serem CC0, não limitam a AGPL-3.0-only do restante -do projeto. A interface do PS5 é apenas referência de sensação (toques curtos, redondos e suaves, confirmar subindo e voltar -descendo); nenhum som da Sony foi usado ou imitado. +Os toques de mover, confirmar e voltar (src/ControlFS.Core/Audio/SoundBank.cs) são os mesmos do Console Mode +(https://github.com/lippdev/consolemode, src/ConsoleMode/Services/UiSoundSynth.cs), outro aplicativo da equipe, sob a +GNU Affero General Public License v3.0, a mesma licença deste projeto: senoide com uma oitava baixinha, ataque rápido e queda, +montados em código (nenhum arquivo de áudio é incluído). O toque de marcar é um quarto som no mesmo estilo, escrito aqui. +Nenhum som de terceiros, de biblioteca ou da Sony é usado: a interface do PS5 é apenas referência de sensação. O Windows e seus serviços não fazem parte deste código aberto. diff --git a/assets/sounds/LICENSE-Kenney-CC0.txt b/assets/sounds/LICENSE-Kenney-CC0.txt deleted file mode 100644 index 1fc5acf8..00000000 --- a/assets/sounds/LICENSE-Kenney-CC0.txt +++ /dev/null @@ -1,22 +0,0 @@ - - - Interface Sounds (1.0) - - Created/distributed by Kenney (www.kenney.nl) - Creation date: 11-02-2020 - - ------------------------------ - - License: (Creative Commons Zero, CC0) - http://creativecommons.org/publicdomain/zero/1.0/ - - This content is free to use in personal, educational and commercial projects. - Support us by crediting Kenney or www.kenney.nl (this is not mandatory) - - ------------------------------ - - Donate: http://support.kenney.nl - Patreon: http://patreon.com/kenney/ - - Follow on Twitter for updates: - http://twitter.com/KenneyNL \ No newline at end of file diff --git a/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt b/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt deleted file mode 100644 index 4ff46a65..00000000 --- a/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt +++ /dev/null @@ -1,21 +0,0 @@ - - - UI SFX Set - - by Kenney Vleugels (Kenney.nl) - - ------------------------------ - - License (Creative Commons Zero, CC0) - http://creativecommons.org/publicdomain/zero/1.0/ - - You may use these assets in personal and commercial projects. - Credit (Kenney or www.kenney.nl) would be nice but is not mandatory. - - ------------------------------ - - Donate: http://support.kenney.nl - Request: http://request.kenney.nl - - Follow on Twitter for updates: - @KenneyNL \ No newline at end of file diff --git a/assets/sounds/back.wav b/assets/sounds/back.wav deleted file mode 100644 index 1722ea1bd4601d4bdcdb527e32a8dc3f718c3e02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4850 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 M!M)B`OR^xxRE(gzue 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≈XHSl|oHN_%Y zW}Cl-KBkv>jp=7OU%D$r$@}G7a)!KE9w?m@Mv}AmF78J*koho=bQKKYThUMKDJ&yr zU<%2F9I^=4BTvX@LIWY6oFG$30q%e+&;a}$xh&*}`BD<}ju+&^u%^v4(zM64#N?DG z0+q@upnKRa-xlwaeh}4X&^uHHQ%D?o!&*>XZ>!(ewrJmKZL~n`jk-bYt4>sN)REc( zh^y=BeD$unObgey=}|^7ZO6JnpKzTg!nE5RJwX$233QH&$Qqd3+{7weBCW|T=$yBp z)@Ty%!1~ZUgB$;%n`j`7G+OF&wOqB48mo+RKXR>c4RzIUr8z%$c5~FRPpX_z>0!TY z_jV8mwbyaLDdsS?w-%?QzX=^>X!eCAnhUE8Xkev)zlK zJ72Fvs0k`lztrYyy7perHTKauY(6vd7?g+)5p zjYq~%YNzX23;u|I`EkPSjBn$3(m;4ATo6}CE~%9~Up@m7j7(fkm1E=$l1(}w_7%Sr zJBbb@hX#%}V35$b@^pI)H$zCy;_u-3R z;&}ilOj~pUg+c}HL~fA|!g67rFd3@faABM&TiiZ{`MBPtg}NitS=WES&!Un2|r=K3kB1e#CzR-eL!I zVmizfspJYt06xWd0gl1>fbSSQg|FbF_+ZezA0NR-@_~FbUjte;Myo;ICD;=($V1qR z&4eM4y)Hxc$`xJ-e+e6f4#G<^iAZD*u7Ove67bznwuq)1J&gN$3w@P#Q$^}3Wrp&T zazZIkLewtmJT+bIp;7IHe#f{(kF(8uC}0AiBwA=9nxvCbHF>DKQ@$fxOWo@JY`+71>nUJIUhFiT z57TubOts5&hqh3wp`B8HRNJX{m3U>0(oJcnj8c{<7nSy^TfL?&*V`Ml=@)D|zktHY zY~h?}mux28q?tvlYJF=v>`~n_%G2NTvPTz>hqfNJCD!jP&CI#-CaIm6L*g;#KeH~h ziSel(t#wovE7|Vu?h@BU*Gky4r(HGO3*DLS)=G*p7^brq+A)2+5lSD@6@X`iAv-#c zJ3|hxEzB3r3#w3GY%YE-hKYH?U6`3K3ipH;!Wm(X&{%j28T&mkNSIIuIxQ6t7C)i7 zfS{gkBA=1VfTr2eJV56z@+iKa1+dkW0k#lqJl1#U{q+D{&>KO9OVmsBw#I5B+lU2} zXAJY=fACn81afo$bgn+cNian4A40nDPAC_Og=~;&mJlplB;$yU?1uQa-g5r!{CT z8UmC&FOJ4i|)H&;=+$ zzHmuc1u^X>+<`sTh+KiJn1b4Y=I!`J)|{OItjB4bGNu^K48h3LpXgcOIS(V&SZBO9 zTGHjTkPc$0tQp_Oy}<9!Q3ULXXSgx>ksN?{Itv}vadHH{uO`#T5E4o9L6&IP_eaqz z^dvJ~-4Hw;FUDH|7utt6W9auF{}cQY z5X>QX=Cg;HSkz>jg%hc$#;nGT5K4*D}7 z%LnLR^dCT5gCLJiW%~dF_W-0do*x73+6VfXrC_Is?6e(i_r3J3Oc}!C>F&4wFZiTYxPkb6ohO@gwqp++j%7~ zgR_%&a9Z*jD2L~LJTWPSGZiP-xB-~A31Z%iJmB2LAD#?`S13?0NE!+iHUv&qe32)t zXamoA!kq+Zuxk0ob@O4hsvwuRLA=8Lx_`14!XIaK>PX zD!$&R8fX*<9K&G(YXsB~J{$fQMZj*TkLrTWLqV1xkj4B_Hw|o2wOR#Or2u5e1#a0N zrFagfI#v9$Kl0B7uN49B5|G&mJ4J{15s-{5A2s$v)j#sC(mUiM1%bW+aMkZ4RRiff zK=Ue|1PCF<8OW}I4pscCUiObv3G3TGYFDLU6(U8aY)4zZEKVSa6 K!2k0Fq5l9fhvdcp diff --git a/assets/sounds/select.wav b/assets/sounds/select.wav deleted file mode 100644 index a024612abe7a86e93146adf992822e4dfb648a53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7604 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 abGfg+!MZX3{(E!v|9|`!^Y8!viTW>j#v`); diff --git a/docs/GUIDE.md b/docs/GUIDE.md index f1158e38..0a4bd263 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -308,7 +308,7 @@ The `controlfs://` scheme is registered in Windows by the installer (and re-regi ## Controller sounds -Menu → Configurações → **Sons do controle** plays short, soft cues for what you do with the controller: a tick when focus moves, a rising note for confirm/open, a falling one for back and a small blip when you mark an item. They are **on by default at medium volume**; choose **desligados** (off), **baixo**, **médio**, **alto** or **máximo** (your choice is kept across restarts and updates). They play only for controller actions (never the keyboard), don't repeat faster than about 12 times a second when you hold the D-pad, use the default Windows audio output and never change a button or how navigation works. The four sounds are short CC0 cues from Kenney's "Interface Sounds" pack, embedded in the app (licence in THIRD_PARTY_NOTICES.md). +Menu → Configurações → **Sons do controle** plays short, soft cues for what you do with the controller: a tick when focus moves, a rising note for confirm/open, a falling one for back and a small blip when you mark an item. They are **on by default at medium volume**; choose **desligados** (off), **baixo**, **médio**, **alto** or **máximo** (your choice is kept across restarts and updates). They play only for controller actions (never the keyboard), don't repeat faster than about 12 times a second when you hold the D-pad, use the default Windows audio output and never change a button or how navigation works. The sounds are the same soft console-style blips as Console Mode, generated in code (credit in THIRD_PARTY_NOTICES.md). ## Screen readers diff --git a/docs/GUIDE.pt-BR.md b/docs/GUIDE.pt-BR.md index 782ad93d..844033cb 100644 --- a/docs/GUIDE.pt-BR.md +++ b/docs/GUIDE.pt-BR.md @@ -308,7 +308,7 @@ O esquema `controlfs://` é registrado no Windows pelo instalador (e de novo ao ## Sons do controle -Menu → Configurações → **Sons do controle** toca sons curtos e suaves para o que você faz com o controle: um tique ao mover o foco, uma nota que sobe ao confirmar/abrir, uma que desce ao voltar e um toque pequeno ao marcar um item. Vêm **ligados em volume médio**; escolha **desligados**, **baixo**, **médio**, **alto** ou **máximo** (a escolha fica salva ao reabrir e ao atualizar). Tocam só nas ações do controle (nunca do teclado), não repetem mais que umas 12 vezes por segundo ao segurar o direcional, usam a saída de áudio padrão do Windows e nunca mudam um botão nem a navegação. Os quatro sons são toques curtos CC0 do pacote "Interface Sounds" de Kenney, embutidos no app (licença em THIRD_PARTY_NOTICES.md). +Menu → Configurações → **Sons do controle** toca sons curtos e suaves para o que você faz com o controle: um tique ao mover o foco, uma nota que sobe ao confirmar/abrir, uma que desce ao voltar e um toque pequeno ao marcar um item. Vêm **ligados em volume médio**; escolha **desligados**, **baixo**, **médio**, **alto** ou **máximo** (a escolha fica salva ao reabrir e ao atualizar). Tocam só nas ações do controle (nunca do teclado), não repetem mais que umas 12 vezes por segundo ao segurar o direcional, usam a saída de áudio padrão do Windows e nunca mudam um botão nem a navegação. Os sons são os mesmos toques macios de console do Console Mode, gerados em código (crédito em THIRD_PARTY_NOTICES.md). ## Leitores de tela diff --git a/src/ControlFS.Core/Audio/SoundBank.cs b/src/ControlFS.Core/Audio/SoundBank.cs index 831033a4..9406d9c8 100644 --- a/src/ControlFS.Core/Audio/SoundBank.cs +++ b/src/ControlFS.Core/Audio/SoundBank.cs @@ -1,57 +1,72 @@ -using System.Reflection; - namespace ControlFS.Core.Audio; /// -/// Os sons do controle (#276, #287): quatro toques curtos do pacote "Interface Sounds" de Kenney (licença CC0, domínio público, -/// redistribuição livre; origem e licença em assets/sounds e THIRD_PARTY_NOTICES.md), já aparados, sem estalo e com a mesma energia -/// (tools/make-sounds.py). Ficam embutidos no assembly; aqui só se aplica o volume do usuário, sempre para baixo a partir do nível -/// mestre (curva perceptiva: 25% soa bem mais baixo que 100%). A interface do PS5 é só referência de sensação: nada da Sony foi usado. +/// Os sons do controle (#276, #287): os mesmos toques do Console Mode (https://github.com/lippdev/consolemode, AGPL-3.0, da mesma +/// equipe; UiSoundSynth), montados em código, então o app não leva nenhum arquivo de áudio: toques curtos e macios (uma +/// senoide com uma oitava baixinha, ataque rápido e queda) em volume baixo, na linha de um menu de console. Mover, confirmar (sobe) +/// e voltar (desce) são os do Console Mode; marcar é um quarto toque no mesmo estilo. A interface do PS5 é só referência de +/// sensação. Saem como WAV PCM de 16 bits, mono, 44,1 kHz, prontos para tocar da memória. /// public static class SoundBank { public const int SampleRate = 44100; - private const int HeaderBytes = 44; - private static readonly Dictionary Masters = []; + /// Uma nota: vai de From a To Hz durante Ms. + private readonly record struct Note(double From, double To, int Ms, double Gain); - private static string FileFor(SoundCue cue) => cue switch + private static Note[] Notes(SoundCue cue) => cue switch { - SoundCue.Move => "move", - SoundCue.Select => "select", - SoundCue.Confirm => "confirm", - _ => "back", + SoundCue.Move => [new(1150, 1000, 45, 0.30)], + SoundCue.Select => [new(880, 940, 55, 0.30)], + SoundCue.Confirm => [new(740, 740, 55, 0.32), new(1110, 1110, 95, 0.34)], + _ => [new(820, 820, 50, 0.30), new(560, 540, 85, 0.30)], }; - /// O WAV do som (PCM 16 bits, mono, 44,1 kHz) no volume de 1 a 100. + /// + /// O WAV do som no volume de 1 a 100. 50 ("médio") é o nível original do Console Mode; 100 é o dobro (nunca satura) e 25 a + /// metade, em curva linear. + /// public static byte[] Render(SoundCue cue, int volume) { - var master = Master(cue); - var wav = (byte[])master.Clone(); - var gain = Math.Pow(Math.Clamp(volume, 1, 100) / 100.0, 1.5); - for (var i = HeaderBytes; i + 1 < wav.Length; i += 2) + var scale = Math.Clamp(volume, 1, 100) / 50.0; + var samples = new List(); + double phase = 0; + foreach (var note in Notes(cue)) { - var sample = (short)Math.Round(BitConverter.ToInt16(master, i) * gain); - wav[i] = (byte)(sample & 0xFF); - wav[i + 1] = (byte)((sample >> 8) & 0xFF); + var count = SampleRate * note.Ms / 1000; + var attack = SampleRate * 3 / 1000; + var release = SampleRate * 6 / 1000; + for (var i = 0; i < count; i++) + { + var t = (double)i / count; + var frequency = note.From + ((note.To - note.From) * t); + phase += 2 * Math.PI * frequency / SampleRate; + var tone = Math.Sin(phase) + (0.25 * Math.Sin(2 * phase)); + var envelope = Math.Exp(-3.2 * t) + * Math.Min(1.0, (double)i / attack) + * Math.Min(1.0, (double)(count - i) / release); + samples.Add((float)(tone / 1.25 * note.Gain * envelope * scale)); + } } - return wav; - } - private static byte[] Master(SoundCue cue) - { - lock (Masters) - { - if (Masters.TryGetValue(cue, out var cached)) return cached; - var name = $"sounds/{FileFor(cue)}.wav"; - using var stream = typeof(SoundBank).Assembly.GetManifestResourceStream(name) - ?? throw new InvalidOperationException($"Som embutido ausente: {name}"); - using var memory = new MemoryStream(); - stream.CopyTo(memory); - var bytes = memory.ToArray(); - if (bytes.Length <= HeaderBytes || bytes[36] != 'd' || bytes[37] != 'a' || bytes[38] != 't' || bytes[39] != 'a') - throw new InvalidOperationException($"Som embutido inválido (esperado WAV PCM de 44 bytes de cabeçalho): {name}"); - return Masters[cue] = bytes; - } + var dataLength = samples.Count * 2; + using var stream = new MemoryStream(44 + dataLength); + using var writer = new BinaryWriter(stream); + writer.Write("RIFF"u8); + writer.Write(36 + dataLength); + writer.Write("WAVEfmt "u8); + writer.Write(16); // cabeçalho PCM + writer.Write((short)1); // PCM + writer.Write((short)1); // mono + writer.Write(SampleRate); + writer.Write(SampleRate * 2); // bytes por segundo + writer.Write((short)2); // alinhamento do bloco + writer.Write((short)16); // bits por amostra + writer.Write("data"u8); + writer.Write(dataLength); + foreach (var sample in samples) + writer.Write((short)Math.Round(Math.Clamp(sample, -1f, 1f) * short.MaxValue)); + writer.Flush(); + return stream.ToArray(); } } diff --git a/src/ControlFS.Core/ControlFS.Core.csproj b/src/ControlFS.Core/ControlFS.Core.csproj index 6e7ba01c..7cfc9188 100644 --- a/src/ControlFS.Core/ControlFS.Core.csproj +++ b/src/ControlFS.Core/ControlFS.Core.csproj @@ -7,8 +7,4 @@ - - - - diff --git a/src/ControlFS.Infrastructure.Windows/Shell/WindowsSoundPlayer.cs b/src/ControlFS.Infrastructure.Windows/Shell/WindowsSoundPlayer.cs index 1840aa68..b716f961 100644 --- a/src/ControlFS.Infrastructure.Windows/Shell/WindowsSoundPlayer.cs +++ b/src/ControlFS.Infrastructure.Windows/Shell/WindowsSoundPlayer.cs @@ -6,7 +6,7 @@ namespace ControlFS.Infrastructure.Windows.Shell; /// /// Toca os sons do controle (#276) pelo dispositivo de áudio padrão do Windows (winmm PlaySound, da memória e assíncrono: -/// volta na hora e um som novo substitui o anterior). Cada som (WAV embutido) é preparado uma vez por volume e fica na memória +/// volta na hora e um som novo substitui o anterior). Cada som (montado em código) é preparado uma vez por volume e fica na memória /// nativa enquanto o app roda. Sem dispositivo de som, PlaySound só devolve falso: nada quebra. /// public sealed partial class WindowsSoundPlayer : ISoundPlayer, IDisposable diff --git a/tests/ControlFS.UnitTests/Core/ControllerSoundsTests.cs b/tests/ControlFS.UnitTests/Core/ControllerSoundsTests.cs index da78fd85..b2265ef9 100644 --- a/tests/ControlFS.UnitTests/Core/ControllerSoundsTests.cs +++ b/tests/ControlFS.UnitTests/Core/ControllerSoundsTests.cs @@ -88,23 +88,23 @@ public void Synthesized_cues_are_short_valid_wav_files_quieter_at_lower_volume_a Assert.True(Peak(wav) > 500, $"{cue}: sem sinal"); } Assert.True(Peak(SoundBank.Render(SoundCue.Confirm, 100)) > Peak(SoundBank.Render(SoundCue.Confirm, 25)) * 3); - Assert.InRange(Peak(SoundBank.Render(SoundCue.Confirm, 100)), 1, (int)(short.MaxValue * 0.6)); // discreto mesmo no máximo + Assert.InRange(Peak(SoundBank.Render(SoundCue.Confirm, 100)), 1, (int)(short.MaxValue * 0.8)); // discreto mesmo no máximo Assert.Equal(4, bytes.Values.Select(Convert.ToBase64String).Distinct().Count()); } [Fact] - public void Every_cue_has_the_same_loudness_and_starts_and_ends_in_silence() + public void Every_cue_starts_and_ends_in_silence_stays_soft_and_the_volume_setting_scales_it() { - var levels = Enum.GetValues().ToDictionary(c => c, c => Rms(SoundBank.Render(c, 60))); - Assert.InRange(levels.Values.Max() / levels.Values.Min(), 1.0, 1.25); // dentro de ~2 dB: nenhum som "grita" foreach (var cue in Enum.GetValues()) { var wav = SoundBank.Render(cue, 100); var peak = Peak(wav); - Assert.True(Math.Abs((int)BitConverter.ToInt16(wav, 44)) < peak * 0.02, $"{cue}: começa fora do silêncio"); - Assert.True(Math.Abs((int)BitConverter.ToInt16(wav, wav.Length - 2)) < peak * 0.02, $"{cue}: termina fora do silêncio"); + Assert.True(Math.Abs((int)BitConverter.ToInt16(wav, 44)) < peak * 0.05, $"{cue}: começa fora do silêncio"); + Assert.True(Math.Abs((int)BitConverter.ToInt16(wav, wav.Length - 2)) < peak * 0.05, $"{cue}: termina fora do silêncio"); + Assert.True(Rms(wav) < 0.25, $"{cue}: alto demais"); } - Assert.True(Rms(SoundBank.Render(SoundCue.Move, 100)) > Rms(SoundBank.Render(SoundCue.Move, 25)) * 4); // 25% soa bem mais baixo + Assert.InRange(Rms(SoundBank.Render(SoundCue.Move, 100)) / Rms(SoundBank.Render(SoundCue.Move, 50)), 1.95, 2.05); // médio = o nível do Console Mode; máximo = o dobro + Assert.True(Rms(SoundBank.Render(SoundCue.Move, 100)) > Rms(SoundBank.Render(SoundCue.Move, 25)) * 3); } private static double Rms(byte[] wav) diff --git a/tools/make-sounds.py b/tools/make-sounds.py deleted file mode 100644 index 449af865..00000000 --- a/tools/make-sounds.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -""" -Gera assets/sounds/*.wav (os sons do controle, #276/#287) a partir de dois pacotes de Kenney em CC0: "Interface Sounds" e "UI Audio". - -Só é preciso rodar de novo para trocar um som; o app usa os WAV já prontos. Requer `pip install soundfile numpy`. -Uso: python tools/make-sounds.py