O Neuroone SDK é uma ferramenta para pesquisadores que lhes permite embrulhar seus próprios modelos de áudio e executá -los em uma DAW usando nosso plug -in de neutone. Oferecemos funcionalidade para carregar os modelos localmente no plug -in e contribuí -los para a lista padrão de modelos disponíveis para qualquer pessoa que execute o plug -in. Esperamos que isso permita que os pesquisadores experimentem facilmente seus modelos em uma DAW, mas também forneçam aos criadores uma coleção de modelos interessantes.
Juce é o padrão da indústria para criar plugins de áudio. Por esse motivo, é necessário o conhecimento do C ++ para poder criar plugins de áudio muito simples. No entanto, é raro que os pesquisadores de áudio da IA tenham uma vasta experiência com C ++ e possam construir um plug -in. Além disso, é um investimento de tempo sério que pode ser gasto desenvolvendo melhores algoritmos. A Neuroone possibilita a criação de modelos usando ferramentas familiares como o Pytorch e com uma quantidade mínima de código Python embrulhando esses modelos, de modo que eles possam ser executados pelo plug -in de Neutone. Colocar um modelo em funcionamento dentro de um DAW pode ser feito em menos de um dia sem a necessidade de código ou conhecimento C ++.
O SDK fornece suporte para buffer automático de entradas e saídas para o seu modelo e a taxa de amostragem em voo e a conversão estéreo-mono. Ele permite um modelo que só pode ser executado com um número predefinido de amostras a serem usadas no DAW em qualquer taxa de amostragem e em qualquer tamanho de buffer sem problemas. Além disso, dentro das ferramentas SDK para benchmarking e perfil estão prontamente disponíveis para que você possa depurar e testar facilmente o desempenho de seus modelos.
Você pode instalar neutone_sdk usando pip:
pip install neutone_sdk
O plug -in de neutone está disponível em https://neutone.space. Atualmente, oferecemos plugins VST3 e AU que podem ser usados para carregar os modelos criados com este SDK. Visite o site para obter mais informações.
Se você deseja apenas envolver um modelo sem passar por uma descrição detalhada do que tudo o que faz, preparamos esses exemplos para você.
O SDK fornece funcionalidade para envolver os modelos Pytorch existentes de uma maneira que possa torná -los executáveis no plug -in VST. Na sua essência, o plug -in está enviando pedaços de amostras de áudio a uma determinada taxa de amostra como entrada e espera a mesma quantidade de amostras na saída. O usuário do SDK pode especificar quais taxas de amostra e tamanho (s) de buffer seus modelos têm o desempenho ideal. O SDK garante que o passe direto do modelo receberá áudio em uma dessas combinações (sample_rate, buffer_size). Estão disponíveis quatro botões que permitem que os usuários do plug -in alimentem parâmetros adicionais para o modelo em tempo de execução. Eles podem ser ativados ou desativados conforme necessário através do SDK.
Usando a função de exportação incluída, uma série de testes é executada automaticamente para garantir que os modelos se comportem conforme o esperado e estejam prontos para serem carregados pelo plug -in.
Ferramentas de benchmarking e criação de criação de perfil estão disponíveis para depuração e teste de modelos embrulhados. É possível comparar a velocidade e a latência de um modelo em uma variedade de combinações simuladas de DAW comum (sample_rate, buffere_size), bem como perfil a memória e o uso da CPU.
Fornecemos vários modelos no diretório de exemplos. Passaremos por um dos modelos mais simples, um modelo de distorção, para ilustrar.
Suponha que tenhamos o seguinte modelo Pytorch. Os parâmetros serão abordados posteriormente, focaremos nas entradas e saídas por enquanto. Suponha que este modelo receba um tensor de forma (2, buffer_size) como uma entrada em que buffer_size é um parâmetro que pode ser especificado.
class ClipperModel ( nn . Module ):
def forward ( self , x : Tensor , min_val : float , max_val : float , gain : float ) -> Tensor :
return torch . clip ( x , min = min_val * gain , max = max_val * gain )Para executar isso dentro do VST, o invólucro mais simples que podemos escrever é subclassificar o WaveFormTowaveFormBase Baseclass.
class ClipperModelWrapper ( WaveformToWaveformBase ):
@ torch . jit . export
def is_input_mono ( self ) -> bool :
return False
@ torch . jit . export
def is_output_mono ( self ) -> bool :
return False
@ torch . jit . export
def get_native_sample_rates ( self ) -> List [ int ]:
return [] # Supports all sample rates
@ torch . jit . export
def get_native_buffer_sizes ( self ) -> List [ int ]:
return [] # Supports all buffer sizes
def do_forward_pass ( self , x : Tensor , params : Dict [ str , Tensor ]) -> Tensor :
# ... Parameter unwrap logic
x = self . model . forward ( x , min_val , max_val , gain )
return x O método que faz a maior parte do trabalho é do_forward_pass . Nesse caso, é apenas uma passagem simples, mas usaremos para lidar com parâmetros posteriormente.
Por padrão, o VST é executado como stereo-stereo , mas quando o mono é desejado para o modelo, podemos usar o is_input_mono e is_output_mono para informar o SDK e ter as entradas e saídas convertidos automaticamente. Se is_input_mono for alterado, um tensor médio (1, buffer_size) será passado como uma entrada em vez de (2, buffer_size) . Se is_output_mono for alterado, do_forward_pass deverá retornar um tensor mono (Shape (1, buffer_size) ) que será duplicado nos dois canais na saída do VST. Isso é feito dentro do SDK para evitar alocações de memória desnecessárias durante cada passagem.
get_native_sample_rates e get_native_buffer_sizes podem ser usados para especificar quaisquer taxas de amostra preferidas ou tamanhos de buffer. Na maioria dos casos, espera -se que eles tenham apenas um elemento, mas é fornecida flexibilidade extra para modelos mais complexos. Caso várias opções sejam fornecidas, o SDK tentará encontrar o melhor para a configuração atual do DAW. Sempre que a taxa de amostragem ou o tamanho do buffer é diferente da do wrapper DAW, é acionada automaticamente que se converte na taxa de amostragem correta ou implementa uma fila FIFO para o tamanho do buffer solicitado ou ambos. Isso incorrerá em uma pequena penalidade de desempenho e adicionará alguma quantidade de atraso. Caso um modelo seja compatível com qualquer taxa de amostra e/ou tamanho de buffer, essas listas podem ser deixadas vazias.
Isso significa que o tensor x no método do_forward_pass é garantido como de forma (1 if is_input_mono else 2, buffer_size) onde buffer_size será escolhido no tempo de execução da lista fornecida no método get_native_buffer_sizes . O Tensor x também estará em uma das taxas de amostragem da lista fornecida no método get_native_sample_rates .
Fornecemos uma função save_neutone_model HINDER que salva modelos no disco. Por padrão, isso converterá modelos para tochcript e executá -los através de uma série de cheques para garantir que eles possam ser carregados pelo plug -in. O arquivo model.nm resultante pode ser carregado dentro do plug -in usando o botão load your own . Leia abaixo como enviar modelos para a coleção padrão visível para todos que usam o plug -in.
Para modelos que podem usar sinais de condicionamento, atualmente fornecemos quatro parâmetros de botão configurável. Dentro do ClipperModelWrapper , definido acima, podemos incluir o seguinte:
class ClipperModelWrapper ( WaveformToWaveformBase ):
...
def get_neutone_parameters ( self ) -> List [ NeutoneParameter ]:
return [ NeutoneParameter ( name = "min" , description = "min clip threshold" , default_value = 0.5 ),
NeutoneParameter ( name = "max" , description = "max clip threshold" , default_value = 1.0 ),
NeutoneParameter ( name = "gain" , description = "scale clip threshold" , default_value = 1.0 )]
def do_forward_pass ( self , x : Tensor , params : Dict [ str , Tensor ]) -> Tensor :
min_val , max_val , gain = params [ "min" ], params [ "max" ], params [ "gain" ]
x = self . model . forward ( x , min_val , max_val , gain )
return x Durante a passagem para a frente, a variável params será um dicionário como o seguinte:
{
"min" : torch . Tensor ([ 0.5 ] * buffer_size ),
"max" : torch . Tensor ([ 1.0 ] * buffer_size ),
"gain" : torch . Tensor ([ 1.0 ] * buffer_size )
} As chaves do dicionário são especificadas na função get_parameters .
Os parâmetros sempre assumirão valores entre 0 e 1 e a função do_forward_pass podem ser usados para fazer qualquer redimensionamento necessário antes de executar o método interno do modelo.
Além disso, os parâmetros enviados pelo plug -in chegam em uma granularidade de amostra. Por padrão, pegamos a média de cada buffer e retornamos um único bóia (como tensor), mas o método aggregate_param pode ser usado para substituir o método de agregação. Consulte o arquivo de exportação completa do Clipper para um exemplo de preservação dessa granularidade.
Alguns modelos de áudio atrasarão o áudio para uma certa quantidade de amostras. Isso depende da arquitetura de cada modelo específico. Para que o sinal molhado e seco que esteja passando pelo plug -in seja alinhado aos usuários para relatar quantas amostras de atraso seu modelo induzem. O calc_model_delay_samples pode ser usado para especificar o número de amostras de atraso. Os modelos RAVE, em média, têm um buffer de atraso (2048 amostras) que é comunicado estaticamente no método calc_model_delay_samples e pode ser visto nos exemplos. Os modelos implementados com o Sobreposição de ADD terão um atraso igual ao número de amostras usadas para crossfading, como visto no invólucro de modelo Demucs ou no exemplo do filtro espectral.
O cálculo do atraso que o seu modelo adiciona pode ser difícil, especialmente porque pode haver várias fontes diferentes de atraso que precisam ser combinadas (por exemplo, atraso de cossfading, atraso de filtro, atraso do buffer de loteahead e / ou redes neurais treinadas em áudio seco e úmido desalinhados) . Vale a pena gastar algum tempo extra testando o modelo em seu DAW para garantir que o atraso esteja sendo relatado corretamente.
Adicionar um buffer lookbehind ao seu modelo pode ser útil para modelos que exigem uma certa quantidade de contexto adicional para produzir resultados úteis. Um buffer lookbehind pode ser ativado facilmente, indicando quantas amostras de lookbehind você precisa no método get_look_behind_samples . Quando esse método retornar um número maior que zero, o método do_forward_pass sempre receberá um tensor de forma (in_n_ch, look_behind_samples + buffer_size) , mas ainda deve retornar um tensor de forma (out_n_ch, buffer_size) das amostras mais recentes.
Recomendamos evitar o uso de um buffer de aparência para trás, quando possível, pois torna seu modelo menos eficiente e pode resultar em cálculos desperdiçados durante cada passe direto. Se estiver usando um modelo puramente convolucional, tente alternar todas as convoluções para convoluções em cache.
É comum os modelos de IA agirem de maneira inesperada quando apresentados com entradas fora das presentes em sua distribuição de treinamento. Fornecemos uma série de filtros comuns (baixo baixo, passe alto, passe de banda, parada de banda) no arquivo neutone_sdk/filters.py. Estes podem ser usados durante o passe direto para restringir o domínio das entradas que entram no modelo. Alguns deles podem induzir uma pequena quantidade de atraso, consulte os exemplos/exemplo_clipper_prefilter.py para um exemplo simples de como configurar um filtro.
O plug -in contém uma lista padrão de modelos destinados a criadores que desejam usá -los durante seu processo criativo. Incentivamos os usuários a enviar seus modelos depois de ficarem felizes com os resultados que obtêm para que possam ser usados pela comunidade em geral. Para o envio, exigimos alguns metadados adicionais que serão usados para exibir informações sobre o modelo destinado a criadores e outros pesquisadores. Isso será exibido no site Neuroone e dentro do plug -in.
Ignorando o modelo anterior Clipper, aqui está um exemplo mais realista baseado em um modelo de overdrive TCN aleatório inspirado no micro-TCN.
class OverdriveModelWrapper ( WaveformToWaveformBase ):
def get_model_name ( self ) -> str :
return "conv1d-overdrive.random"
def get_model_authors ( self ) -> List [ str ]:
return [ "Nao Tokui" ]
def get_model_short_description ( self ) -> str :
return "Neural distortion/overdrive effect"
def get_model_long_description ( self ) -> str :
return "Neural distortion/overdrive effect through randomly initialized Convolutional Neural Network"
def get_technical_description ( self ) -> str :
return "Random distortion/overdrive effect through randomly initialized Temporal-1D-convolution layers. You'll get different types of distortion by re-initializing the weight or changing the activation function. Based on the idea proposed by Steinmetz et al."
def get_tags ( self ) -> List [ str ]:
return [ "distortion" , "overdrive" ]
def get_model_version ( self ) -> str :
return "1.0.0"
def is_experimental ( self ) -> bool :
return False
def get_technical_links ( self ) -> Dict [ str , str ]:
return {
"Paper" : "https://arxiv.org/abs/2010.04237" ,
"Code" : "https://github.com/csteinmetz1/ronn"
}
def get_citation ( self ) -> str :
return "Steinmetz, C. J., & Reiss, J. D. (2020). Randomized overdrive neural networks. arXiv preprint arXiv:2010.04237."Confira a documentação dos métodos dentro do núcleo.py, bem como o modelo de overdrive aleatório no site e no plug -in para entender onde cada campo será exibido.
Para enviar um modelo, abra um problema no repositório do GitHub. Atualmente precisamos do seguinte:
model.nm em saída pela função save_neutone_model Helper O SDK fornece três ferramentas da CLI que podem ser usadas para depurar e testar modelos embrulhados.
Exemplo:
$ python -m neutone_sdk.benchmark benchmark-speed --model_file model.nm
INFO:__main__:Running benchmark for buffer sizes (128, 256, 512, 1024, 2048) and sample rates (48000,). Outliers will be removed from the calculation of mean and std and displayed separately if existing.
INFO:__main__:Sample rate: 48000 | Buffer size: 128 | duration: 0.014±0.002 | 1/RTF: 5.520 | Outliers: [0.008]
INFO:__main__:Sample rate: 48000 | Buffer size: 256 | duration: 0.028±0.003 | 1/RTF: 5.817 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 512 | duration: 0.053±0.003 | 1/RTF: 6.024 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 1024 | duration: 0.106±0.000 | 1/RTF: 6.056 | Outliers: []
INFO:__main__:Sample rate: 48000 | Buffer size: 2048 | duration: 0.212±0.000 | 1/RTF: 6.035 | Outliers: [0.213]
A execução do benchmark de velocidade executará automaticamente entradas aleatórias através do modelo a uma taxa de amostragem de 48000 e tamanhos de buffer de (128, 256, 512, 1024, 2048) e relatará o tempo médio necessário para executar a inferência por um buffer. A partir disso, o 1/RTF é calculado, o que representa o quanto o modelo é mais rápido do que o modelo. À medida que esse número aumenta, o modelo usará menos recursos no DAW. É necessário que esse número seja maior que 1 para que o modelo possa ser executado em tempo real na máquina em que a referência é executada.
As taxas de amostragem e tamanhos de buffer sendo testados, bem como o número de vezes que o benchmark é repetido internamente para calcular as médias e o número de threads usados para o cálculo estão disponíveis como parâmetros. Execute python -m neutone_sdk.benchmark benchmark-speed --help para obter mais informações. Ao especificar taxas de amostragem ou tamanhos de buffer personalizados, cada indivíduo precisa ser passado para a CLI separadamente. Por exemplo: --sample_rate 48000 --sample_rate 44100 --buffer_size 32 --buffer_size 64 .
Embora o benchmark de velocidade deva ser rápido, pois os modelos geralmente são necessários para ser em tempo real, é possível ficar preso se o modelo estiver muito lento. Certifique -se de escolher um número apropriado de taxas de amostra e tamanhos de buffer para testar.
Exemplo:
$ python -m neutone_sdk.benchmark benchmark-latency model.nm
INFO:__main__:Native buffer sizes: [2048], Native sample rates: [48000]
INFO:__main__:Model exports/ravemodel/model.nm has the following delays for each sample rate / buffer size combination (lowest delay first):
INFO:__main__:Sample rate: 48000 | Buffer size: 2048 | Total delay: 0 | (Buffering delay: 0 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 1024 | Total delay: 1024 | (Buffering delay: 1024 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 512 | Total delay: 1536 | (Buffering delay: 1536 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 256 | Total delay: 1792 | (Buffering delay: 1792 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 128 | Total delay: 1920 | (Buffering delay: 1920 | Model delay: 0)
INFO:__main__:Sample rate: 48000 | Buffer size: 128 | Total delay: 1920 | (Buffering delay: 1920 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 256 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 512 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 1024 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0)
INFO:__main__:Sample rate: 44100 | Buffer size: 2048 | Total delay: 2048 | (Buffering delay: 2048 | Model delay: 0) A execução do benchmark de velocidade calculará automaticamente a latência do modelo nas combinações de sample_rate=(44100, 48000) e buffer_size=(128, 256, 512, 1024, 2048) . Isso fornece uma visão geral do que acontecerá para as configurações comuns de DAW. O atraso total é dividido em atraso em buffer e atraso do modelo. O atraso do modelo é relatado pelo criador do modelo no invólucro do modelo, conforme explicado acima. O atraso em buffer é automaticamente calculado pelo SDK, levando em consideração a combinação de (sample_rate, buffer_size) especificado pelo wrapper (os nativos) e o especificado pelo DAW em tempo de execução. A execução do modelo em sua combinação nativa (sample_rate, buffer_size) incorrerá no atraso mínimo.
Semelhante à referência de velocidade acima, as combinações testadas de (sample_rate, buffer_size) podem ser especificadas na CLI. Execute python -m neutone_sdk.benchmark benchmark-latency --help para obter mais informações.
$ python -m neutone_sdk.benchmark profile --model_file exports/ravemodel/model.nm
INFO:__main__:Profiling model exports/ravemodel/model.nm at sample rate 48000 and buffer size 128
STAGE:2023-09-28 14:34:53 96328:4714960 ActivityProfilerController.cpp:311] Completed Stage: Warm Up
30it [00:00, 37.32it/s]
STAGE:2023-09-28 14:34:54 96328:4714960 ActivityProfilerController.cpp:317] Completed Stage: Collection
STAGE:2023-09-28 14:34:54 96328:4714960 ActivityProfilerController.cpp:321] Completed Stage: Post Processing
INFO:__main__:Displaying Total CPU Time
INFO:__main__:-------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------
Name Self CPU % Self CPU CPU total % CPU total CPU time avg CPU Mem Self CPU Mem # of Calls
-------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------
forward 98.54% 799.982ms 102.06% 828.603ms 26.729ms 0 b -918.17 Kb 31
aten::convolution 0.12% 963.000us 0.95% 7.739ms 175.886us 530.62 Kb -143.50 Kb 44
...
...
Full output removed from GitHub.
A ferramenta de perfil executará o modelo a uma taxa de amostragem de 48000 e um tamanho de buffer de 128 sob o Profiler Pytorch e produzir uma série de insights, como o tempo total da CPU, o uso total da memória da CPU (por função) e o uso da memória da CPU agrupada (por grupo de chamadas de função). Isso pode ser usado para identificar gargalos no seu código de modelo (mesmo dentro da chamada do modelo na chamada do_forward_pass ).
Semelhante ao benchmarking, ele pode ser executado em diferentes combinações de taxas de amostra e tamanhos de buffer, além de diferentes números de encadeamentos. Execute python -m neutone_sdk.benchmark profile --help para obter mais informações.
Congratulamo -nos com quaisquer contribuições para o SDK. Adicione os tipos sempre que possível e use o formatador black para obter legibilidade.
O roteiro atual é:
O projeto Audacitorch foi uma grande inspiração para o desenvolvimento do SDK. Confira aqui