Manguebyte

Além do JUCE

Onde ele já vai além do JUCE

O JUCE é a referência do que um framework de áudio completo contém, e a primeira meta do Manguebyte é igualá-lo. Esta página trata da segunda meta: os pontos em que o Manguebyte já faz algo que o JUCE não faz, e como cada afirmação pode ser conferida.

O JUCE está muito à frente em abrangência, e a última seção diz onde. Nada no Manguebyte contém código do JUCE: veja o ADR 0004.

Três regras mantêm esta página honesta

  1. Toda afirmação aponta para um teste. Se não dá para verificar rodando algo do repositório, não entra na lista.
  2. Toda frase sobre o JUCE tem fonte, e descreve o branch master do JUCE na versão 9.0.4 (outubro de 2026). O JUCE continua andando; a equipe anunciou um “AudioProcessor v2” que mira vários destes pontos, então alguns deles podem mudar em versões futuras.
  3. Os limites vêm ao lado da afirmação, em vez de ficarem para o leitor descobrir.

Resumo

As quinze afirmações. Cada linha leva à seção correspondente, mais abaixo.
Afirmação JUCE 9.0.4 Manguebyte Conferido por
1. Tempo da automação Por bloco, em AudioProcessor Na amostra exata plugin automation_lands_on_its_exact_frame_and_updates_the_parameterclap automation_lands_on_its_frame_when_processing_in_place
2. Tamanho do bloco e som Fica por conta de cada plugin Idêntico bit a bit em qualquer tamanho de bloco, com teste manguebyte the_song_is_the_same_whatever_the_block_sizepluck sounds_the_same_whatever_the_block_size
3. Mudanças vindas da interface ou de um estado restaurado Vistas por consulta periódica, ou por listeners que podem rodar na thread de áudio Entregues como eventos, no mesmo fluxo da automação plugin changes_made_outside_the_host_reach_the_plugin_as_events
4. Segurança em tempo real Uma convenção Uma chamada ao heap durante o processamento faz o CI falhar dsp processors_do_not_use_the_heapdsp a_synth_handles_a_flood_of_events_without_using_the_heap
5. Controle por nota Por canais MPE Eventos de expressão e de modulação por nota dsp expression_and_modulation_reach_one_note_and_parameters_reach_alldsp notes_with_identifiers_overlap_on_one_key_and_end_separately
6. CLAP Não existe de forma nativa Nativo, passa no clap-validator ci CI job: CLAP validationduck plugins/duck/src/lib.rs
7. Testar um plugin Precisa de um host ou de uma bancada feita à mão Num teste unitário, com Harness pluck plays_the_pitch_it_is_asked_forduck automation_lands_on_its_frame_with_audio_running_through
8. Parâmetros e estado entre threads Sincronizados por cada plugin Atômicos; não há o que travar params state_from_another_version_loads_as_far_as_it_canparams params_are_shareable_across_threads
9. Licença AGPLv3 ou comercial MIT ou Apache-2.0 file LICENSE-MITfile LICENSE-APACHE
10. Lentidão no silêncio Uma guarda que cada plugin põe no próprio callback Posta pelo framework em volta de cada plugin e de cada callback de dispositivo clap numbers_too_small_to_hear_are_zero_while_the_plugin_runs_and_only_thenvst3 numbers_too_small_to_hear_are_zero_while_the_plugin_runs_and_only_then
11. Hospedagem Não hospeda CLAP; um objeto chamado de todas as threads; mudanças de parâmetro sem frame CLAP e VST 3; regras de thread nos tipos; cada evento no seu frame host the_hosted_plugin_sounds_exactly_as_the_plugin_does_alonehost the_hosted_plugin_sounds_exactly_as_the_plugin_does_alone
12. Loudness e medidores de broadcast Nenhum na documentação Loudness BS.1770 / R 128, true peak, VU, PPM, correlação, conferidos com os sinais de teste da EBU analysis cases_1_and_2_a_steady_tone_reads_its_level_on_all_three_scalesanalysis cases_15_to_19_a_sine_is_read_at_its_crest_wherever_the_samples_fall
13. Tempo do OSC Listeners na thread de mensagens ou de rede Bundles com time tag caem no seu frame; todos os tipos do OSC 1.0; TCP osc a_time_tag_lands_on_its_exact_frameosc every_frame_of_a_block_can_be_hit
14. Altura e análise por bandas Nenhuma na documentação Altura por YIN e McLeod; bandas de oitava IEC e ponderação analysis every_test_tone_is_found_within_a_centanalysis a_full_scale_sine_reads_0_db_in_its_band
15. Estado do teclado Listeners que podem rodar na thread de áudio Atômicos que a interface consulta; sem listener, sem lock midi the_keyboard_state_does_not_use_the_heapmidi a_key_that_goes_on_sounding_is_never_read_as_silent

Automação na amostra exata

O problema

O host grava a automação com a posição de cada amostra. Se o plugin só fica sabendo do valor uma vez por bloco, uma mudança que deveria acontecer no frame 300 de um bloco de 512 é aplicada no frame 0 ou no 512. Automação rápida vira degraus, e o tamanho dos degraus depende do buffer configurado no host.

No JUCE

AudioProcessor::processBlock recebe o áudio e um buffer de MIDI. As mudanças de parâmetro não fazem parte da chamada; o processador lê o valor atual de cada parâmetro, que o host atualiza entre um bloco e outro. Em maio de 2026, um membro da equipe do JUCE disse que a automação com precisão de amostra não ficaria pronta para o primeiro lançamento do JUCE 9 (tópico no fórum).

No Manguebyte

A chamada de processamento traz um EventBuffer em que cada mudança de parâmetro tem o frame em que cai. split_at_events renderiza até cada evento, aplica-o e continua, usando AudioBuffer::windowed para estreitar o buffer no próprio lugar. Nada é copiado nem alocado.

Confira

automation_lands_on_its_exact_frame_and_updates_the_parameter agenda mudanças nos frames 100, 101 e 250 e confere a saída amostra por amostra. automation_lands_on_its_frame_when_processing_in_place faz o mesmo pela interface C do CLAP, e um teste de mesmo nome faz pelo VST 3, em que o host envia uma fila de pontos por parâmetro e todos os pontos são entregues. (O wrapper VST3 do JUCE aplica o último ponto de cada fila: processParameterChanges em juce_audio_plugin_client_VST3.cpp.)

O VST 3 tem uma armadilha própria. O host informa cada mudança duas vezes: ao processador, com o número do frame, e ao controlador, na thread principal, para que um editor possa acompanhar. Se a cópia do controlador for gravada no parâmetro, o plugin salta para um valor um pouco adiantado ou atrasado até o ponto seguinte corrigir. Enquanto os blocos estão rodando, o wrapper do Manguebyte guarda a cópia do controlador só para exibição; what_the_controller_is_told_does_not_disturb_running_automation confere isso.

Mostrar o teste
Rust
#[test]
fn automation_lands_on_its_exact_frame_and_updates_the_parameter() {
    let mut harness = Harness::<Level>::new(48_000.0, 64);
    harness.automate(100, "level", 2.0);
    harness.automate(101, "level", 3.0);
    harness.automate(250, "level", 0.5);

    let output = harness.process(&ones(2, 300));
    let left = output.channel(0);
    assert_eq!(&left[..100], &[1.0; 100]);
    assert_eq!(left[100], 2.0);
    assert_eq!(&left[101..250], &[3.0; 149]);
    assert_eq!(&left[250..], &[0.5; 50]);
    assert_eq!(output.channel(1), left);

    // Anything polling the parameter sees where automation left it.
    assert_eq!(harness.params().level.value(), 0.5);
    assert_eq!(harness.position(), 300);
}
De crates/manguebyte-plugin/src/harness.rs no commit c5835bf, como está no arquivo.

O som não depende do tamanho de bloco do host

O problema

Os hosts entregam blocos de tamanhos diferentes, e alguns mudam o tamanho a cada chamada. Se alguma coisa no plugin estiver presa às bordas do bloco, o mesmo projeto soa um pouco diferente com 64 e com 1024 amostras, e o bounce offline sai diferente do que se ouviu na mixagem.

No JUCE

Nada no framework impede nem testa isso. Cabe a cada autor de plugin dividir os blocos nos eventos à mão e perceber quando alguma coisa depende do tamanho do bloco.

No Manguebyte

Como os eventos trazem o seu frame e o sintetizador reinicia a voz antes de cada nota nova, a saída é função da linha do tempo, e não de como ela foi fatiada. Isso é testado, não presumido.

Confira

the_song_is_the_same_whatever_the_block_size renderiza uma música polifônica, com efeitos e automação, de seis maneiras: 64, 256, 997 e 1024 frames, um padrão irregular de 1, 511, 7, 300, 64, 2, 129 e blocos maiores que o máximo do próprio instrumento. Os resultados têm de ser iguais bit a bit. Os dois plugins de exemplo têm o mesmo teste: o instrumento e o efeito.

Na primeira vez em que rodou, o teste falhou. Uma voz que terminava no meio de um bloco continuava avançando até o fim dele, de modo que a nota seguinte partia de um estado que dependia do tamanho do bloco. O teste existe para achar exatamente isso, e a correção faz parte do contrato de Voice.

Mostrar o teste
Rust
#[test]
fn the_song_is_the_same_whatever_the_block_size() {
    let reference = render(256, &[256]);
    assert!(reference.peak() > 0.1, "the reference render is silent");

    for (max_block_size, pattern) in [
        (64, &[64][..]),
        (1024, &[1024]),
        // Odd lengths that never line up with a note boundary.
        (997, &[997]),
        // A host that changes its mind on every call.
        (512, &[1, 511, 7, 300, 64, 2, 129]),
        // Blocks larger than the player's own, which it must split itself.
        (48, &[5_000]),
    ] {
        let other = render(max_block_size, pattern);
        assert!(
            other == reference,
            "blocks of {pattern:?} (max {max_block_size}) changed the output"
        );
    }
}
De crates/manguebyte/tests/offline.rs no commit c5835bf, como está no arquivo.

Toda mudança de parâmetro chega ao plugin como evento

O problema

A automação do host não é a única coisa que muda um parâmetro. O usuário gira um botão na janela do próprio plugin, ou o host restaura um estado salvo. Essas escritas não chegam pela lista de eventos do host, então um plugin que acompanha o valor só pelos eventos nunca fica sabendo delas.

No JUCE

O processador descobre consultando o parâmetro, ou por meio de um listener. Os listeners de parâmetro são chamados de forma síncrona na thread que fez a mudança, que durante a automação do host é a thread de áudio, e as classes de attachment do framework respondem postando na fila de mensagens do sistema operacional, o que toma um lock (tópico no fórum, relato de bug).

No Manguebyte

Não existem listeners. Antes de cada bloco, ParamWatcher compara cada parâmetro com o valor que o plugin viu por último e acrescenta um evento no frame 0 para cada um que mudou, antes dos eventos do próprio host. O fluxo de eventos fica, assim, completo: o plugin que o segue nunca precisa consultar nada, e nenhum código do usuário roda como efeito colateral de uma escrita.

Confira

changes_made_outside_the_host_reach_the_plugin_as_events usa um plugin de teste que nunca lê o seu parâmetro, só os eventos. Ele passa para uma escrita direta, para um estado restaurado e para automação do host no mesmo frame, que tem precedência.

Segurança em tempo real é testada, não prometida

O problema

O código da thread de áudio não pode alocar nem liberar memória: o alocador pode tomar um lock ou pedir páginas ao sistema operacional, e qualquer um dos dois pode durar mais que os poucos milissegundos de um bloco. Uma alocação perdida é um estalo audível que só aparece sob carga.

No JUCE

A segurança em tempo real é uma regra da documentação. O framework não confere o código do usuário contra ela, e os seus próprios attachments de parâmetro a quebram, como descrito na seção anterior.

No Manguebyte

A suíte de testes troca o alocador global por um que conta. Qualquer alocação, realocação ou liberação feita enquanto um processador está processando faz o teste falhar e, com ele, o CI. A mesma guarda envolve a chamada process do wrapper CLAP, medida do lado do host na interface C.

Confira

processors_do_not_use_the_heap e os testes vizinhos cobrem todos os processadores, a FFT, um sintetizador recebendo mais notas do que tem vozes e mudanças de parâmetro feitas durante a execução. Um teste da própria guarda garante que ela perceberia. Pela interface do CLAP, heap_calls tem de ser zero para automação, notas, mudanças informadas ao host e o bloco seguinte a uma restauração de estado.

A guarda já se pagou. No macOS ela acusou uma chamada ao heap dentro de process: um plugin de teste gravava um valor por meio de um mutex padrão, que nessa plataforma aloca na primeira vez em que é travado.

Mostrar o teste
Rust
/// Runs `work` and panics if it used the heap in any way.
fn assert_no_heap_use(what: &str, work: impl FnOnce()) {
    HEAP_CALLS.with(|calls| calls.set(0));
    WATCHING.with(|watching| watching.set(true));
    work();
    WATCHING.with(|watching| watching.set(false));
    let calls = HEAP_CALLS.with(Cell::get);
    assert_eq!(
        calls, 0,
        "{what} used the heap {calls} time(s) while processing"
    );
}

/// Prepares a processor, then runs it over blocks of several sizes with the
/// allocator watched.
fn check<P: Processor<f32>>(what: &str, mut processor: P) {
    processor.prepare(&SPEC);
    let mut buffer = AudioBuffer::<f32>::new(SPEC.num_channels, SPEC.max_block_size);
    assert_no_heap_use(what, || {
        for frames in [256, 1, 64, 255, 256] {
            buffer.set_num_frames(frames);
            for channel in buffer.iter_channels_mut() {
                channel.fill(0.25);
            }
            processor.process(&mut buffer);
        }
        processor.reset();
        buffer.set_num_frames(256);
        processor.process(&mut buffer);
    });
}
Trecho de crates/manguebyte-dsp/tests/realtime.rs no commit c5835bf.

Expressão e modulação por nota

O problema

Fazer bend, crescer ou filtrar uma só nota de um acorde exige um jeito de endereçar essa nota. O mesmo vale para o sistema de modulação de um host que queira mover um parâmetro em uma voz sem sobrescrever o valor que o usuário definiu.

No JUCE

O controle por nota existe por meio do MPE, que dá a cada nota um canal MIDI próprio. O modelo de processador não tem eventos de expressão de nota nem modulação distinta de mudar o valor de um parâmetro, e é por isso que o wrapper CLAP de terceiros acrescenta interfaces de extensão próprias para isso (clap-juce-extensions).

No Manguebyte

As notas têm identidade (NoteRef: canal, tecla e um id opcional), então duas notas na mesma tecla podem se sobrepor. NoteExpression molda uma nota. ParamModulation soma um deslocamento a um parâmetro, opcionalmente para uma única nota, e nunca muda o valor guardado; por isso não briga com a automação nem vai parar num preset salvo. Synth encaminha cada um à voz que está tocando a nota.

Confira

expression_and_modulation_reach_one_note_and_parameters_reach_all e notes_with_identifiers_overlap_on_one_key_and_end_separately no sintetizador; an_instrument_takes_notes_expression_modulation_and_a_sidechain faz o mesmo pelo CLAP.

CLAP nativo

No JUCE

Gerar plugins CLAP não tem suporte nativo. O módulo de cliente de plugin cobre VST, VST3, AU, AUv3, LV2 e AAX. O CLAP constava para o JUCE 9 no roadmap de 2024 e não está nas versões 9.0.x; o caminho hoje é o clap-juce-extensions, não oficial.

No Manguebyte

export_clap!(MyPlugin) é a integração inteira. O modelo de eventos do CLAP é o mesmo sobre o qual este framework foi construído, então nada é aproximado na entrada.

Confira

O job de CI compila os dois plugins de exemplo e roda neles o clap-validator, do próprio projeto CLAP, e falha diante de qualquer teste reprovado. No commit em que este site foi escrito, o validador rodou 44 testes em cada um: o instrumento passou em 35 e pulou 9, o efeito passou em 33 e pulou 11, e nenhum dos dois falhou em algum. Os testes pulados são de coisas que os plugins ainda não têm: descoberta de presets (3 testes), áudio em 64 bits (2) e as extensões que deixam o host reconfigurar as portas de áudio (3). O instrumento também pula um teste que lhe envia áudio, porque não tem entrada de áudio, e o efeito pula os três testes de notas, porque não recebe notas.

cargo mangue bundle grava o .clap ao lado do .vst3, no layout de cada sistema, e cargo mangue validate roda nele o validador do projeto CLAP e, no .vst3, o da Steinberg; um job de CI roda a ferramenta de ponta a ponta em Windows, Linux e macOS. O juce_add_plugin do JUCE não aceita o formato CLAP.

Um plugin pode ser testado sem host

No JUCE

Exercitar o caminho de áudio de um plugin exige um host, ou uma bancada de testes que o autor monta em volta de AudioProcessor.

No Manguebyte

Harness é essa bancada. Ele prepara o plugin, agenda notas e automação numa linha do tempo, corta o tempo em qualquer padrão de tamanhos de bloco, mantém um transporte andando e salva e restaura o estado. O som do plugin vira algo que um teste unitário comum consegue conferir, numa máquina de CI sem placa de som.

Confira

Os testes do instrumento de exemplo conferem que ele fica em silêncio até ser tocado, que toca a altura pedida (por FFT), que a automação e a modulação por nota mudam o som, que o estado vai e volta, e que a saída não depende do tamanho do bloco. Os testes do efeito de exemplo alimentam o sidechain do mesmo jeito.

Mostrar o teste
Rust
#[test]
fn plays_the_pitch_it_is_asked_for() {
    use manguebyte::dsp::fft::{Complex, RealFft};

    let mut harness = Harness::<Pluck>::new(RATE, 512);
    harness.params().reverb.set(0.0);
    // No resonant peak, so the fundamental is the loudest partial.
    harness.params().resonance.set(0.7);
    harness.note(0, 40_000, 69, 1.0); // A4, 440 Hz
    let sound = harness.render(32_768);

    let mut fft = RealFft::<f32>::new(32_768);
    let mut spectrum = vec![Complex::ZERO; fft.spectrum_len()];
    fft.forward(sound.channel(0), &mut spectrum);
    let loudest = (1..spectrum.len())
        .max_by(|&a, &b| spectrum[a].norm_sqr().total_cmp(&spectrum[b].norm_sqr()))
        .unwrap();
    let hertz = loudest as f64 * RATE / 32_768.0;
    assert!((hertz - 440.0).abs() < 2.0, "loudest partial at {hertz} Hz");
}
De plugins/pluck/src/lib.rs no commit c5835bf, como está no arquivo.

Parâmetros e estado não têm como disputar

No JUCE

getStateInformation e setStateInformation podem ser chamadas de qualquer thread que não seja a de áudio enquanto processBlock está rodando, e o framework não as serializa; cada plugin tem de fazer isso (tópico no fórum). Entre o JUCE 5.0 e o 5.2, a árvore de onde o estado era salvo podia ficar atrasada em relação aos valores reais dos parâmetros (tópico no fórum).

No Manguebyte

Um parâmetro é um atômico, e todos os métodos recebem &self. Salvar lê os atômicos; carregar escreve neles. Existe uma única cópia de cada valor, então o estado não fica defasado, e o compilador recusa código que compartilhe qualquer outra coisa entre threads sem sincronização. O estado salvo guarda valores reais indexados por identificador, de modo que uma faixa pode ser redesenhada numa versão futura sem mudar o som de uma sessão antiga.

Confira

state_from_another_version_loads_as_far_as_it_can e params_are_shareable_across_threads, no crate de parâmetros.

Licença

No JUCE

O JUCE é oferecido sob a AGPLv3 ou sob uma licença comercial.

No Manguebyte

O Manguebyte está sob MIT ou Apache-2.0, à sua escolha: sem copyleft, sem taxa por desenvolvedor e sem tela de abertura, para produtos abertos e fechados.

Confira

Os dois textos de licença no repositório: MIT e Apache-2.0.

Um som que some não deixa a thread de áudio lenta

O problema

Um som que vai sumindo não chega a zero. A memória dos filtros e as caudas de reverb encolhem até os valores ficarem subnormais: ainda diferentes de zero, e dezenas de vezes mais lentos de calcular. O plugin passa a custar mais justamente no silêncio depois da nota. Os processadores têm uma configuração por thread que arredonda esses números para zero, e usá-la é prática comum.

No JUCE

A configuração está disponível como ScopedNoDenormals, uma guarda que o autor de cada plugin põe no começo de processBlock. O modelo de plugin já vem com ela. Código que não partiu do modelo, ou um callback escrito depois, fica desprotegido até alguém notar.

No Manguebyte

O próprio framework segura a guarda: os wrappers CLAP e VST 3 e o motor standalone em volta de Plugin::process, e os streams de dispositivo em volta de cada callback. O autor do plugin não tem nada para lembrar. A configuração é devolvida na saída, então o código do host roda como ele espera.

Confira

Um teste nos dois wrappers (CLAP, VST 3) faz um plugin informar, de dentro de process, o que o processador faz com um número subnormal, e depois confere que a thread do host voltou à configuração dela, inclusive depois de o plugin entrar em pânico. Medido no efeito de exemplo, 4.000 blocos de entrada subnormal levaram 442 ms sem a guarda e 31 ms com ela.

Hospedar um plugin: as regras de thread nos tipos, cada evento no seu frame

O problema

O host chama o plugin a partir de dois tipos de thread, e os formatos dizem qual chamada pertence a qual. Errar nisso derruba plugins no uso real, e nada impede um host de errar. Um host que queira passar automação a um plugin com precisão também precisa de um jeito de dizer em que frame cai cada mudança.

No JUCE

O JUCE 9.0.4 não hospeda plugins CLAP. Nos formatos que ele hospeda, o plugin é um único AudioPluginInstance, um AudioProcessor que o host chama tanto da thread de mensagens quanto da thread de áudio; qual método pode rodar onde está na documentação, e o compilador não confere nada disso. A interface de hospedagem é processBlock(AudioBuffer, MidiBuffer): os eventos MIDI trazem a posição da amostra, mas uma mudança de parâmetro, feita por AudioProcessorParameter::setValue, não traz nenhuma, então o host só consegue pôr uma mudança num frame exato cortando o bloco ali. O JUCE anunciou automação com precisão de amostra para uma atualização futura do JUCE 9.

No Manguebyte

O manguebyte-host carrega plugins CLAP e VST 3 em duas metades. A metade da thread principal (parâmetros, estado, ativação) não é Send, então o código que carregaria estado a partir da thread de áudio não compila; a metade de áudio é Send, é a única que pode processar, e processa por &mut self. Cada evento do buffer de eventos do framework chega ao plugin no seu frame: os eventos CLAP com os seus deslocamentos e, no VST 3, uma fila por parâmetro com cada ponto no seu frame, notas com ids, pressão por nota e expressão de nota.

Confira

Doc-tests que precisam falhar na compilação, em ClapPlugin e Vst3Plugin, mostram que a metade da thread principal não pode ser enviada a outra thread. the_hosted_plugin_sounds_exactly_as_the_plugin_does_alone, para CLAP e para VST 3, carrega o instrumento de exemplo pelo seu ponto de entrada real e exige que a saída seja idêntica bit a bit à do mesmo plugin rodando dentro do processo pelo Harness, em quatro arranjos de blocos, com automação de cinco parâmetros, notas com ids, expressão e pitch bend; tirar qualquer um dos tipos de evento muda a saída, então a identidade não é a de dois caminhos surdos concordando. automation_lands_on_its_frame_through_the_host envia 59 pontos de VST 3 espaçados de 61 frames.

Loudness, true peak e medidores de broadcast, prontos para usar

O problema

Tudo o que entrega áudio para broadcast ou streaming tem de medir loudness segundo a ITU-R BS.1770 e a EBU R 128, e o true peak. É fácil errar sutilmente o gating, as janelas e a ponderação K em alguma taxa de amostragem.

No JUCE

A API documentada do JUCE 9.0.4 não tem classe nem função de loudness, LUFS, ponderação K ou true peak, nem medidor VU, PPM, de correlação ou de balanço (lista de classes). As ferramentas de nível são AudioBuffer::getRMSLevel e getMagnitude, AudioDeviceManager::LevelMeter e dsp::BallisticsFilter. No fórum, os usuários escrevem os seus (loudness, true peak).

No Manguebyte

O manguebyte-analysis tem um LoudnessMeter (momentâneo, de curto prazo, integrado, faixa de loudness, máximos), um TruePeakMeter (BS.1770-4, Anexo 2, 4x por padrão), um medidor VU, um PPM com a temporização Tipo I da IEC 60268-10 e um medidor de correlação e balanço. Eles rodam na thread de áudio, numa memória fixada quando são criados, e qualquer thread os lê por atômicos, sem esperar.

Confira

ebu_tech_3341.rs sintetiza os sinais de teste da EBU Tech 3341 e exige os casos 1 a 6 e 9 a 14 dentro de 0,1 LU, e os casos de true peak 15 a 23 dentro de +0,2/−0,4 dB; ebu_tech_3342.rs faz o mesmo para a faixa de loudness, casos 1 a 4. O ponteiro do VU chega a 99% em 300 ms ±3 ms, com 1,0 a 1,5% de sobressinal, de 44,1 a 192 kHz. As leituras são idênticas até o bit em qualquer tamanho de bloco, e medir não faz nenhuma chamada ao heap.

Open Sound Control que cai na amostra

O problema

Os bundles OSC trazem um time tag para que um controlador ou um programa de live coding diga quando algo deve acontecer. Um motor de áudio que aplica as mensagens quando chegam, ou uma vez por bloco, joga esse tempo fora.

No JUCE

O OSCReceiver chama os seus listeners na thread de mensagens ou direto na thread de rede; a documentação dele não descreve entrega dentro do callback de áudio nem agendamento por time tag. O OSCArgument documenta cinco tipos de argumento (int32, float32, string, blob, cor). O OSCAddressPattern documenta os curingas do OSC 1.0, e o OSCSender e o OSCReceiver documentam só UDP.

No Manguebyte

O manguebyte-osc decodifica numa thread de rede e entrega eventos fixos à thread de áudio por filas wait-free, e uma mensagem num bundle com time tag cai no frame mais próximo do seu time tag. Todos os tipos de argumento listados na especificação do OSC 1.0 são aceitos, inclusive arrays aninhados; o curinga // proposto para o OSC 1.1 (Freed e Schmeder, 2009) é reconhecido; e TCP com o enquadramento SLIP do OSC 1.1 fica ao lado do UDP.

Confira

a_time_tag_lands_on_its_exact_frame, every_frame_of_a_block_can_be_hit e where_an_event_lands_does_not_depend_on_the_block_size (400 eventos, sete padrões de bloco, frames idênticos); ler na thread de áudio não faz nenhuma chamada ao heap. codec.rs codifica os exemplos da especificação byte a byte e faz ida e volta com todos os tipos.

Detecção de altura e análise em frações de oitava

No JUCE

A lista de classes documentada do JUCE 9.0.4 não tem detecção de altura nem analisador de bandas em frações de oitava ou classe de ponderação A e C (classes); ela oferece dsp::FFT e dsp::WindowingFunction como peças, e a exibição de espectro aparece nos tutoriais como código que o leitor escreve.

No Manguebyte

O PitchDetector oferece o YIN e o método de McLeod, com um valor de confiança e o nome da nota mais próxima, refinado entre amostras por interpolação de banda limitada. O SpectrumAnalyzer dá bandas de oitava a 1/24 de oitava nas frequências exatas da IEC 61260-1 e as ponderações A e C da IEC 61672-1, com balística e retenção de pico, entregando quadros inteiros a uma interface por um buffer triplo wait-free.

Confira

pitch.rs: senoides, dentes de serra, ondas quadradas, tons sem a fundamental e tons com um segundo harmônico forte, de 55 Hz a 2 kHz, são lidos dentro de 2 cents a 44,1 e 48 kHz, sem erros de oitava. spectrum.rs: uma senoide em escala cheia lê 0 dB na sua banda, com margem de 0,01 dB, e as bandas de terço de oitava de ruído branco e rosa somam certo dentro de 0,05 dB.

Um teclado que a interface lê sem ficar ouvindo

No JUCE

O MidiKeyboardState informa as mudanças de tecla aos seus listeners de forma síncrona, na thread que o atualiza, e a documentação avisa que o callback pode acontecer a partir de uma thread de callback de áudio (listener). A interface pode consultar isNoteOn() em vez disso, mas a documentação não garante que atualizações ou leituras sejam livres de locks ou de alocação, e a classe acompanha teclas, não pedais.

No Manguebyte

O midi::keyboard::KeyboardState guarda teclas, velocidades e o pedal de sustain em atômicos. A thread de áudio o atualiza sem código de listener, sem lock e sem chamada ao heap; a interface o consulta. Quem lê nunca vê uma tecla que não foi tocada, e nunca vê uma tecla sustentada ficar muda enquanto passa da mão para o pedal.

Confira

the_keyboard_state_does_not_use_the_heap (20.000 mensagens aleatórias, com leituras intercaladas), e a_key_that_goes_on_sounding_is_never_read_as_silent e an_interface_can_read_while_another_thread_writes.

Onde o JUCE está à frente

O JUCE está em produção há mais de vinte anos, e isso aparece. Hoje ele tem, e o Manguebyte não:

  • um toolkit de interface gráfica completo, com janelas de editor de plugin em todos os sistemas e formatos (os editores do Manguebyte só têm janelas no Win32, e nenhum foi aberto num host à mão ainda);
  • os formatos de plugin Audio Unit e AAX;
  • hospedagem de plugins AU e LV2, varredura de plugins e os editores dos plugins hospedados (o Manguebyte hospeda plugins CLAP e VST 3, sem os editores);
  • mais blocos de DSP, e mais formatos de arquivo;
  • iOS e Android;
  • documentação, tutoriais e uma comunidade grande.

O JUCE_PARITY.md acompanha essa distância módulo a módulo, e o roadmap diz em que ordem ela está sendo fechada.

Esta página segue o docs/BEYOND_JUCE.md do repositório no commit c5835bf. Onde o README é mais novo, a página segue o README: o limite da seção 6, sobre editores e LV2, e a lista de onde o JUCE está à frente. O limite da seção 2 também cita o que o teste no REAPER encontrou.