Como criar um aplicativo Android usando Kotlin e C++, capaz de responder a comandos MIDI e reproduzir sons sintetizados a partir de arquivos de SoundFont.
Introdução
O Musical Instrument Digital Interface (MIDI) é um protocolo amplamente utilizado no âmbito musical para comunicação entre instrumentos eletrônicos, computadores e outros dispositivos compatíveis. Por meio de mensagens digitais, o MIDI permite o envio de comandos como notas musicais, alterações de intensidade (velocity) e controle de parâmetros diversos, proporcionando uma integração robusta entre equipamentos musicais. No contexto do MIDI, destaca-se o papel dos controladores e dos sintetizadores. Um controlador MIDI é um dispositivo que gera comandos MIDI, mas não possui capacidade de produção sonora própria. Por outro lado, os sintetizadores são responsáveis por interpretar essas mensagens MIDI e gerar os sons correspondentes, seja por hardware dedicado ou via software.
A criação de sintetizadores via software revolucionou o meio musical ao permitir maior acessibilidade e flexibilidade. Entre as soluções populares para síntese via software, o FluidSynth se destaca como uma biblioteca open-source capaz de interpretar arquivos de soundfont, que são coleções de amostras de áudio organizadas de forma a reproduzir sons de instrumentos musicais.
Implementar um aplicativo para Android que integre essas tecnologias de áudio pode ser bastante prático para os usuários finais, pois smartphones e tablets são facilmente acessíveis, porém exige aos desenvolvedores o entendimento de sua arquitetura. Os aplicativos Android são estruturados principalmente na Java Virtual Machine (JVM), permitindo o uso de linguagens como Java e Kotlin. Enquanto Java é uma linguagem madura e amplamente utilizada, Kotlin tem ganhado destaque por sua sintaxe concisa, segurança contra nulidade (null safety) e integração completa ao ecossistema Android. Porém, para aplicações que demandam alto desempenho, como é o caso do processamento de áudio em tempo real, é necessário recorrer a componentes nativos implementados em C ou C++. Por meio da Java Native Interface (JNI), é possível integrar essas linguagens ao ambiente Android, combinando a eficiência do código nativo com a flexibilidade do desenvolvimento na JVM.
Este artigo apresenta, além da conceituação necessária para o entendimento das tecnologias envolvidas, um guia prático para a implementação de um aplicativo sintetizador MIDI para Android. O projeto proposto integra Kotlin, C e C++ para criar uma solução funcional. O aplicativo é capaz de receber comandos MIDI de um controlador conectado via cabo USB-OTG, e de reproduzir sons baseados em soundfonts utilizando a biblioteca FluidSynth. Por meio desta implementação, serão abordados conceitos teóricos, práticas de desenvolvimento e desafios técnicos inerentes a este tipo de aplicação.
O projeto do aplicativo exemplo está no repositório do GitHub, disponível através da seguinte URL:
- https://github.com/robsonsmartins/android-midi-synth (Licença MIT).
MIDI (Musical Instrument Digital Interface)
O MIDI (Musical Instrument Digital Interface) é um protocolo padrão de comunicação musical digital criado em 1983 para interligar instrumentos eletrônicos, computadores e outros dispositivos. Diferentemente de arquivos de áudio, o MIDI não transporta som em si, mas sim instruções digitais que definem como o som deve ser reproduzido. O protocolo MIDI é essencial no meio musical, pois permite que dispositivos diversos interajam de maneira padronizada. Por exemplo, um tecladista pode usar um controlador MIDI para acionar sons armazenados em um sintetizador, sem que ambos precisem ser do mesmo fabricante.
O funcionamento do MIDI baseia-se em mensagens digitais enviadas de um dispositivo transmissor (controlador) para um receptor (sintetizador). Essas mensagens podem conter informações como:
- Nota a ser tocada (dó, ré, mi, etc.);
- Intensidade da nota (velocity);
- Início e fim da reprodução da nota;
- Alterações em parâmetros de timbre, volume ou efeitos.
O controlador MIDI é um dispositivo que envia comandos MIDI, mas não produz som diretamente. Exemplos incluem teclados controladores, pads e superfícies de controle. O sintetizador MIDI é um dispositivo que interpreta mensagens MIDI e gera os sons correspondentes. Podem ser instrumentos físicos (via hardware) ou programas de computador (via software).
Fonte: https://nektartech.com/article/blog/all-you-need-to-know-about-midi/
As mensagens MIDI são pacotes de dados que contêm informações sobre os eventos musicais. Elas se dividem em dois tipos principais:
- Mensagens de Canal: Relacionadas à reprodução de notas e controle de parâmetros.
- Mensagens do Sistema: Usadas para sincronização e configurações globais.
| Código Numérico | Tipo de Mensagem | Descrição |
|---|---|---|
| 0x90 | Note On | Indica o início de uma nota (código da nota e velocity). |
| 0x80 | Note Off | Indica o fim de uma nota. |
| 0xB0 | Control Change (CC) | Altera parâmetros como volume, pan, ou efeitos. |
| 0xC0 | Program Change | Troca o timbre ou instrumento ativo. |
| 0xE0 | Pitch Bend | Altera a altura da nota de forma contínua. |
Fonte: https://midi.org/summary-of-midi-1-0-messages
| Código Numérico | Tipo de Mensagem | Descrição |
|---|---|---|
| 0xF0 | System Exclusive (Sysex) | Transmissão de dados específicos do fabricante. |
| 0xF8 | Timing Clock | Sincroniza dispositivos MIDI. |
| 0xFA/0xFC/0xFB | Start/Stop/Continue | Controla início, pausa e retomada de sequências. |
Fonte: https://midi.org/summary-of-midi-1-0-messages
A maioria dos dispositivos encontrados atualmente são compatíveis com o protocolo MIDI 1.0, como descrito acima. No entanto, a MIDI Manufacturers Association (MMA) introduziu oficialmente em 2020, uma nova versão do protocolo original, chamada de MIDI 2.0. Ela mantém a compatibilidade com o MIDI 1.0, mas traz diversas melhorias e novos recursos que visam expandir as possibilidades criativas e técnicas dos músicos e desenvolvedores. Entre as novidades mais esperadas estão:
- Resolução Aumentada: O MIDI 2.0 oferece uma resolução muito maior para mensagens de controle, permitindo ajustes mais precisos de parâmetros como pitch bend, volume e outros controles contínuos.
- Comunicação Bidirecional: Agora, os dispositivos podem se comunicar de forma mais inteligente, negociando automaticamente suas capacidades e configurando parâmetros sem a intervenção manual do usuário.
- Expressividade Avançada: Com suporte nativo ao MIDI Polyphonic Expression (MPE), o MIDI 2.0 permite que cada nota tenha controles individuais para parâmetros como vibrato ou aftertouch, proporcionando maior expressividade.
- Protocolo Baseado em JSON: Além do formato tradicional de mensagens, o MIDI 2.0 inclui um esquema de configuração baseado em JSON (JavaScript Object Notation), tornando mais simples para os desenvolvedores implementar configurações dinâmicas e modernas.
- Expansibilidade: O novo protocolo foi projetado para ser flexível, permitindo futuras adições sem perder compatibilidade com o padrão atual.
O MIDI 2.0 representa um passo significativo na evolução da tecnologia musical, fornecendo aos músicos e produtores ferramentas mais poderosas para criar e controlar música digitalmente. Embora sua adoção ainda esteja atualmente em andamento, já existem alguns dispositivos e softwares compatíveis, prometendo revolucionar o fluxo de trabalho musical.
Conexão MIDI
Atualmente existem dois tipos de conexão possível com equipamentos compatíveis com o protocolo MIDI. A primeira, mais clássica, se faz por meio de conectores DIN-5, conhecida como interface MIDI. Esse tipo de conexão fornece uma comunicação serial assíncrona com isolação elétrica (via opto-acoplamento) entre os equipamentos.
Fonte: https://nektartech.com/article/blog/all-you-need-to-know-about-midi/
Os equipamentos mais modernos, no entanto, utilizam conectores Universal Serial Bus (USB) tipo B (USB-B), e implementam o protocolo da classe USB-MIDI. Esse tipo de conexão permite uma melhor compatibilidade com computadores. Para interligar o equipamento ao computador, basta usar um cabo USB-A para USB-B convencional.
Fonte: https://www.mozartproject.org/what-is-the-usb-port-in-piano-keyboard-for/
Para interligar equipamentos mais clássicos, que possuem conectores MIDI padrão DIN-5 a computadores ou outros dispositivos compatíveis com USB, existem no mercado cabos adaptadores conhecidos como “cabo MIDI”, de diferentes formatos, qualidades e preços. Esses adaptadores possuem em uma ponta um conector USB-A, compatível com computadores, e na outra ponta, os conectores DIN-5 no mesmo padrão das portas MIDI dos instrumentos musicais.
Fonte: https://cscltf.en.made-in-china.com/product/WFiTLItluAGm/China-USB-MIDI-Cable-Converter-to-PC-Music-Studio-Adapter-2-in-1-for-Piano-Keyboard-Interface-Wire-Plug-High-Quality-Music-Editing-Cord-Cable.html
Se o objetivo for interligar um equipamento MIDI com conector USB a um smartphone ou tablet que possui um conector USB-C (como é o caso dos dispositivos com Android), deve ser utilizado um cabo ou adaptador chamado de USB-OTG (On-The-Go).
Fonte: https://www.samsung.com/au/support/mobile-devices/what-is-an-otg-on-the-go-cable
Um adaptador OTG (também chamado de cabo OTG ou conector OTG) permite conectar um dispositivo USB de tamanho normal, ou um cabo USB-A ao smartphone ou tablet, por meio da porta USB-C. Ele permite a transferência de dados, bem como a conectividade com diversos periféricos, transformando o smartphone ou tablet em um host, ou seja, fazendo com que ele se comporte como se fosse um computador.
Sintetizadores de Software e Soundfonts
Os sintetizadores de software são ferramentas fundamentais na produção musical moderna. Diferentemente dos sintetizadores baseados em hardware, que são dispositivos físicos dedicados à geração de som, os sintetizadores de software são programas que simulam ou criam sons de maneira digital. Eles oferecem uma vasta gama de possibilidades criativas, desde emulações de instrumentos tradicionais até a geração de sons completamente inovadores. Dentre suas principais vantagens, destacam-se:
- Custo acessível: Geralmente de custo mais baixo que seus equivalentes de hardware.
- Flexibilidade: Permitem ajustes e personalizações detalhadas dos sons.
- Integração com DAWs: Podem ser usados diretamente em Digital Audio Workstations, como Ableton Live, Logic Pro e FL Studio, dentre outros.
- Portabilidade: Funcionam em computadores ou dispositivos móveis, eliminando a necessidade de transportar equipamentos adicionais.
Esses sintetizadores geralmente utilizam soundfonts, que são arquivos contendo amostras de áudio e informações sobre como essas amostras devem ser reproduzidas para criar sons realistas ou abstratos. Os formatos mais comuns incluem SF2 e SF3, cada um com suas características específicas.
Existem várias opções populares de sintetizadores de software que podem ser utilizadas conforme as necessidades específicas de um projeto musical ou de desenvolvimento. Dentre os sintetizadores de software mais conhecidos:
- Kontakt: Um sampler poderoso e amplamente utilizado na indústria, desenvolvido pela Native Instruments. Ele oferece suporte a uma grande biblioteca de instrumentos virtuais, chamados de VST (Virtual Studio Technology).
- Serum: Um sintetizador voltado para música eletrônica, conhecido por sua interface intuitiva e capacidade de criar sons complexos.
- Dexed: Um sintetizador gratuito baseado no Yamaha DX7, ideal para recriar sons clássicos de síntese FM (Frequency Modulation).
- ZynAddSubFX: Um sintetizador de código aberto que oferece múltiplas técnicas de síntese e uma ampla gama de possibilidades sonoras.
Cada um desses sintetizadores possui suas próprias vantagens e áreas de especialização, tornando-os adequados para diferentes tipos de usuários e aplicações.
FluidSynth
O FluidSynth é uma biblioteca de software amplamente utilizada para síntese de áudio em tempo real. Desenvolvido em linguagem C, é um projeto open-source licenciado sob a LGPL (GNU Lesser General Public License), versão 2.1. Esse licenciamento permite que a biblioteca seja embarcada em aplicações comerciais e não-comerciais. No entanto, caso alterações sejam feitas na própria biblioteca FluidSynth dentro de uma aplicação, o código-fonte deve ser disponibilizado de forma aberta, de acordo com os termos da LGPL.
O FluidSynth foi projetado para reproduzir sons a partir de soundfonts nos formatos SF2 e SF3, oferecendo alta qualidade de áudio e baixa latência. Pode ser usado como uma aplicação independente ou integrado em outros programas como uma biblioteca. Sua flexibilidade e desempenho o tornam uma escolha popular entre desenvolvedores de áudio e músicos. Suas características principais incluem:
- Síntese em tempo real: Capaz de processar e reproduzir áudio com baixa latência.
- Compatibilidade com soundfonts: Suporte aos formatos SF2 e SF3, amplamente usados na produção musical.
- API robusta: Permite controle detalhado sobre os sons gerados.
- Open-source: Seu código é público, permitindo modificações e adaptações.
- Portabilidade: Compatível com várias plataformas: Linux, Windows, ou Android, por exemplo, com a possibilidade de ser integrada como biblioteca em aplicações escritas nas mais diversas linguagens de programação, como C/C++, Python, Java/Kotlin, etc.
A seguir é demonstrado um exemplo simples em linguagem C de como usar a API da biblioteca FluidSynth para reproduzir uma nota musical:
#include <fluidsynth.h>
int main() {
// FluidSynth initialization
fluid_settings_t* settings = new_fluid_settings();
fluid_synth_t* synth = new_fluid_synth(settings);
// Load the soundfont
if (fluid_synth_sfload(synth, "soundfont.sf2", 1) == FLUID_FAILED) {
printf("Error loading soundfont.\n");
return 1;
}
// Audio driver initialization
fluid_audio_driver_t* adriver = new_fluid_audio_driver(settings, synth);
// Plays a note (channel: 0, note: C4, velocity: 100)
fluid_synth_noteon(synth, 0, 60, 100);
// Wait for 1 sec
sleep(1);
// Stops the note
fluid_synth_noteoff(synth, 0, 60);
// Free resources
delete_fluid_audio_driver(adriver);
delete_fluid_synth(synth);
delete_fluid_settings(settings);
// Exit
return 0;
}
Esse exemplo ilustra os seguintes passos essenciais:
- Criação de configurações e inicialização do sintetizador.
- Carregamento de um arquivo de soundfont.
- Execução de uma nota musical e encerramento da nota.
- Liberação dos recursos.
Com sua versatilidade e amplo suporte, o FluidSynth é uma excelente biblioteca para desenvolvedores da área musical. Sua possibilidade de integração em aplicações como sintetizadores MIDI permite criar experiências musicais ricas e responsivas em múltiplas plataformas.
Soundfonts
Os arquivos soundfont desempenham um papel crucial na síntese de áudio digital, servindo como uma coleção de amostras de som e informações sobre como essas amostras devem ser reproduzidas. Introduzidos originalmente pela Creative Labs na década de 1990, os soundfonts foram concebidos para uso em placas de som, como a Sound Blaster AWE32, permitindo que músicos e desenvolvedores incorporassem sons realistas em suas composições e aplicações.
Os soundfonts evoluíram ao longo do tempo, resultando em diferentes formatos, cada um com suas vantagens e desvantagens, dentre as quais podem-se destacar:
SF2 (SoundFont 2.0):
- O formato mais amplamente suportado e utilizado.
- Estrutura organizada que permite incluir vários instrumentos e ajustes detalhados.
- Armazena as amostras de sons no formato PCM (Pulse Code Modulation), não comprimido.
- Vantagem: Compatibilidade com uma ampla gama de sintetizadores de software e hardware.
- Desvantagem: Tamanho de arquivo grande, tornando-o menos eficiente para projetos de grande escala.
SF3 (SoundFont 3.0):
- Uma extensão do formato SF2, com suporte para compressão das amostras.
- Amostras de áudio comprimidas em formato OGG Vorbis. Este é um formato de compressão de áudio com perdas (lossy compression), conhecido por oferecer uma boa qualidade sonora em relação ao tamanho do arquivo.
- Vantagens:
- Redução de tamanho de arquivo. As amostras comprimidas ocupam significativamente menos espaço do que no formato SF2. Por isso, é ideal para aplicações móveis ou com restrições de armazenamento.
- Qualidade ajustável. A taxa de compressão pode ser configurada para equilibrar qualidade de áudio e tamanho do arquivo.
- Desvantagens:
- Perda de qualidade. Por ser um formato comprimido com perdas, pode haver uma ligeira degradação da qualidade sonora em comparação com as amostras originais, especialmente em configurações de compressão muito alta.
- Compatibilidade limitada. Nem todos os sintetizadores suportam SF3, sendo importante verificar a compatibilidade antes de utilizá-lo.
SFZ:
- Um formato baseado em texto que descreve amostras de som e sua configuração.
- Vantagens:
- Flexibilidade para personalizações avançadas.
- Suporte para amostras de alta qualidade e configurações detalhadas.
- Desvantagens:
- Pode ser mais complexo de configurar e requer editores dedicados para edição manual.
- Nem todos os sintetizadores suportam SFZ.
Um arquivo soundfont pode conter diversos instrumentos e bancos de sons (banks). Os bancos permitem organizar diferentes timbres e instrumentos dentro de um único arquivo, facilitando a seleção de sons para sintetizadores ou aplicações musicais.
Cada instrumento em um soundfont é criado a partir de uma ou mais amostras de som (áudio gravado) combinadas com informações de configuração, como:
- Regiões de notas (quais notas utilizam cada amostra).
- Envelopes de amplitude (como o som evolui ao longo do tempo).
- LFOs (oscilações de baixa frequência para efeitos como vibrato).
Os soundfonts podem ser criados ou editados utilizando ferramentas específicas, como:
- Polyphone: Um editor gratuito e intuitivo que permite criar e modificar arquivos SF2 e SF3.
- Vienna SoundFont Studio: Ferramenta proprietária oficial da Creative Labs para edição de soundfonts.
- Awave Studio: Ferramenta proprietária com suporte a múltiplos formatos de áudio, incluindo soundfonts.
Fonte: https://www.polyphone.io/en/documentation/manual/index
Esses programas permitem que usuários gravem suas próprias amostras ou ajustem os parâmetros dos instrumentos, criando arquivos personalizados para uso em sintetizadores ou aplicações musicais.
Além disso, há alguns repositórios públicos disponíveis na internet que disponibilizam arquivos de soundfont para download, alguns com licenciamento que permite o livre uso em aplicações comerciais ou não-comerciais. Um exemplo desse tipo de repositório é o Polyphone SoundFonts.
Sistema Operacional Android e seus Aplicativos
O sistema operacional Android, desenvolvido pelo Google, é amplamente utilizado em dispositivos móveis como smartphones e tablets. Sua arquitetura é projetada para oferecer flexibilidade, desempenho e segurança, combinando diferentes camadas que interagem de forma eficiente. Essas camadas incluem o kernel, o espaço de execução nativa e a máquina virtual Java (JVM). A arquitetura em camadas do Android foi projetada para reforçar a segurança do sistema. Cada aplicativo roda em um ambiente isolado, conhecido como sandbox, o que impede que aplicativos maliciosos interfiram em outros processos ou acessem dados sem permissão. Essas camadas trabalham juntas para proteger tanto o dispositivo quanto os dados do usuário.
Fonte: https://developer.android.com/guide/platform
Kernel
Atua como uma barreira entre o hardware e o software, garantindo que apenas códigos autorizados interajam com os recursos do sistema. O Android utiliza o kernel do Linux como base de seu sistema operacional. Essa escolha fornece recursos essenciais, como:
- Gerenciamento de memória: Permite o uso eficiente de recursos limitados em dispositivos móveis.
- Gerenciamento de processos: Coordena a execução de múltiplos aplicativos de forma segura e eficiente.
- Drivers de hardware: Proporciona suporte para comunicação direta com componentes físicos, como câmeras, sensores e interfaces de áudio.
- Segurança: Implementa mecanismos como SELinux, que reforçam o controle de acesso e previnem ataques de malware.
A menos que seja um fornecedor de componentes de hardware para smartphones, raramente se desenvolve software na camada de kernel de um sistema operacional Android.
Android Runtime
No Android, os aplicativos geralmente são escritos em Java e/ou Kotlin, linguagens que são compiladas para bytecode e executadas em uma máquina virtual (chamada de Java Virtual Machine – JVM). Inicialmente, o Android utilizava a Dalvik VM, mas, a partir do Android 5.0, a ART (Android Runtime) tornou-se a máquina virtual padrão. A ideia de rodar aplicativos dentro de uma máquina virtual adiciona uma camada de abstração, dificultando ataques diretos ao código do aplicativo.
Em relação a Dalvik, a ART oferece melhorias significativas, como:
- Compilação antecipada (AOT): Compila aplicativos antes da execução, reduzindo o tempo de inicialização.
- Eficiência de memória: Melhor gerenciamento de recursos.
- Execução mais rápida: Oferece melhor desempenho em relação à Dalvik.
Quanto às linguagens Java e/ou Kotlin, ambas podem ser utilizadas para escrever aplicativos Android. As duas são linguagens de programação de paradigma orientado a objetos (POO). As principais diferenças entre elas são:
Linguagem Java:
- Histórico e popularidade: Java foi a linguagem original para o desenvolvimento Android, com uma trajetória que remonta a 1995. Amplamente utilizada em diversas plataformas, possui uma base de código legado extensa, sendo ainda muito relevante para manutenções e projetos legados no ecossistema Android.
- Sintaxe e verbosidade: Java apresenta uma sintaxe mais verbosa, o que pode aumentar a quantidade de código necessário para tarefas comuns. Isso pode resultar em um desenvolvimento menos ágil, especialmente em projetos mais complexos.
- Null safety: Não possui controle de nulidade nativo. O tratamento de exceções por ponteiros nulos é feito manualmente, o que aumenta o risco de erros relacionados à nulidade no código.
- Compatibilidade: Como linguagem original do Android, Java é totalmente compatível com todas as versões do sistema operacional, além de ser facilmente integrado a qualquer projeto Android.
- Recursos modernos: Embora suporte conceitos como lambdas e programação funcional, isso foi adicionado posteriormente e de forma limitada. Java carece de algumas funcionalidades modernas que facilitam o desenvolvimento em comparação a linguagens mais recentes.
- Ferramentas e suporte: O suporte ferramental para Java é robusto, com integração completa ao Android Studio e vasta documentação disponível. A comunidade de desenvolvedores Java é grande, oferecendo uma ampla variedade de recursos.
- Desempenho: O desempenho de aplicativos Android escritos em Java é sólido, dado que a linguagem compila para bytecode e é executada na ART (Android Runtime). A estabilidade e eficiência são bem estabelecidas.
- Suporte oficial: Embora o Google continue oferecendo suporte para Java, ele perdeu o status de linguagem preferencial para o desenvolvimento de novos aplicativos Android, sendo mantido principalmente para projetos já existentes.
Linguagem Kotlin:
- Histórico e popularidade: Introduzida em 2011 pela JetBrains, Kotlin foi oficialmente reconhecida como linguagem oficial para Android em 2017. Desde então, tem sido amplamente adotada para novos projetos, sendo recomendada pelo Google para desenvolvimentos modernos.
- Sintaxe e concisão: Kotlin foi projetada para ser mais concisa e moderna, exigindo menos linhas de código para realizar tarefas comuns. Isso reduz a probabilidade de erros e aumenta a produtividade dos desenvolvedores.
- Null safety: Kotlin inclui suporte nativo para null safety, exigindo que casos nulos sejam tratados explicitamente no código. Esse recurso diminui a ocorrência de erros relacionados à nulidade, tornando o desenvolvimento mais seguro.
- Compatibilidade: É totalmente interoperável com Java, o que permite que ambos coexistam em um mesmo projeto. Essa compatibilidade torna Kotlin uma escolha fácil para projetos que já possuem código legado em Java.
- Recursos modernos: Kotlin oferece funcionalidades avançadas como funções de extensão, coroutines para operações assíncronas e classes de dados (data classes). Esses recursos permitem a criação de aplicativos mais eficientes e modernos, adequados às necessidades contemporâneas.
- Ferramentas e suporte: A integração com o Android Studio é completa, com suporte robusto da JetBrains e do Google. A documentação oficial é clara e abrangente, e a comunidade de desenvolvedores está crescendo rapidamente.
- Desempenho: O desempenho de aplicativos em Kotlin é equivalente ao de Java, já que ambos compilam para bytecode e executam na ART. A sintaxe concisa de Kotlin pode ajudar a reduzir potenciais gargalos durante o desenvolvimento.
- Suporte oficial: Kotlin é a linguagem preferida para novos projetos Android, com suporte direto do Google. Sua adoção é incentivada para acompanhar as inovações no ecossistema Android.
Java continua sendo uma escolha para projetos legados e para desenvolvedores que já possuem experiência com a linguagem. No entanto, Kotlin supera Java em produtividade, segurança e recursos modernos, sendo a escolha recomendada para novos desenvolvimentos Android.
Espaço de Execução Nativa
Embora a maior parte dos aplicativos Android seja desenvolvida em linguagens que rodam na JVM (como Java e/ou Kotlin), o Android também oferece suporte a execução nativa. Códigos em linguagens como C e/ou C++ podem ser executados diretamente, utilizando bibliotecas e recursos nativos do sistema. Essa camada é utilizada principalmente para:
- Desempenho crítico: Como em aplicações de jogos ou processadores de áudio em tempo real.
- Interoperabilidade com hardware: Quando é necessário interagir diretamente com o hardware, como câmeras ou dispositivos conectados.
No entanto, para integrar código nativo com aplicativos escritos em Java e/ou Kotlin, é necessário utilizar a JNI (Java Native Interface). A JNI é uma ponte que permite que os códigos nativos sejam chamados a partir da JVM, e vice-versa, garantindo a comunicação entre essas camadas de execução.
Quanto às linguagens C e/ou C++, ambas podem ser utilizadas para escrever código nativo no Android. As principais diferenças entre elas são:
Linguagem C:
- Paradigma de programação: C é uma linguagem de programação procedural, o que significa que se baseia em chamadas de funções e estruturas de controle para o fluxo do programa. O código é organizado em procedimentos ou funções que operam em dados.
- Tipagem e segurança de tipos: Oferece uma tipagem mais flexível e menos rígida, mas isso pode levar a problemas de segurança de tipos, como erros de conversão implícita de tipos.
- Gestão de memória: Utiliza funções como malloc() e free() para alocação e desalocação dinâmica de memória. A gestão de memória é manual e pode ser propensa a erros como vazamentos de memória e ponteiros pendentes.
- Bibliotecas padrão: A biblioteca padrão de C (C Standard Library) é limitada e oferece funcionalidades básicas para manipulação de strings, entrada/saída, e gestão de memória.
- Suporte a classes e objetos: C não possui suporte nativo a POO. Qualquer tentativa de simular POO em C geralmente resulta em um código mais complexo e menos legível (uso de structs e ponteiros, por exemplo).
- Controle de fluxo e estruturas de dados: C oferece estruturas de controle básicas, como if, for, while, e estruturas de dados como arrays e structs, mas sem suporte a encapsulamento e herança.
- Compatibilidade: É totalmente compatível com a JNI para se comunicar com o aplicativo Android escrito em Java e/ou Kotlin.
Linguagem C++:
- Paradigma de programação: C++ é uma linguagem de programação multiparadigma, mas é mais conhecida por seu suporte à programação orientada a objetos (POO). Isso permite a modelagem de problemas como coleções de objetos interativos, facilitando a gestão de sistemas complexos e a reutilização de código.
- Tipagem e segurança de tipos: Introduz um sistema de tipos mais robusto e seguro, incluindo templates e a capacidade de sobrecarga de operadores e funções, permitindo uma maior flexibilidade e segurança no uso de tipos.
- Gestão de memória: Introduz operadores new e delete para alocação e desalocação de memória, além de suporte a construtores e destrutores que facilitam a gestão de recursos e melhoram a segurança e eficiência da memória.
- Bibliotecas padrão: A biblioteca padrão de C++ (Standard Template Library, STL) é muito mais rica e inclui suporte a coleções genéricas, algoritmos, iteradores e outras utilidades que facilitam a programação genérica e orientada a objetos.
- Suporte a classes e objetos: C++ introduz o conceito de classes e objetos, permitindo o encapsulamento de dados e funções, herança e polimorfismo. Isso facilita a modelagem de sistemas complexos e a reutilização de código.
- Controle de fluxo e estruturas de dados: Além das estruturas de controle herdadas de C, o C++ oferece suporte a classes e objetos, que permitem a criação de estruturas de dados complexas com comportamentos associados. Além disso, C++ também oferece suporte a tratamento de exceções com try-catch, o que pode simplificar a manipulação de situações de erro.
- Compatibilidade: Com o uso de extern “C” nas chamadas a função, fica totalmente compatível com a JNI para se comunicar com o aplicativo Android escrito em Java e/ou Kotlin. Ainda é possível misturar código C++ com código C, ou mesmo usar apenas paradigma estruturado (sem POO) com a linguagem C++.
Para ter código nativo (seja em C e/ou C++) no aplicativo, é necessário utilizar o Android NDK para acessar as diversas bibliotecas nativas da plataforma diretamente a partir do código.
Android Native MIDI API
Os aplicativos Android MIDI costumam usar a midi API para se comunicar com o serviço Android MIDI. Esses aplicativos dependem principalmente da classe MidiManager para descobrir, abrir e fechar um ou mais objetos MidiDevice, além de transmitir dados de cada dispositivo e para eles pelas portas de entrada e saída MIDI.
Fonte: https://developer.android.com/ndk/guides/audio/midi
Essa abordagem, no entanto, supõe que o aplicativo inteiro roda na JVM (escrito em Java e/ou Kotlin), incluindo a implementação do processador de mensagens MIDI e o sintetizador, caso esta seja a finalidade da aplicação (síntese MIDI).
No caso de um aplicativo que roda código nativo, por exemplo, o sintetizador FluidSynth escrito em C embarcado na aplicação, e que se comunica com o código Java/Kotlin via JNI, essa abordagem insere demasiada latência. O seguinte fluxo demonstra claramente esse efeito:
- Os comandos MIDI disparados pelo controlador chegam como mensagens via USB-MIDI ao Android;
- As mensagens MIDI são interceptadas pela JVM e repassadas para o aplicativo;
- O aplicativo usa a midi API para receber e tratar as mensagens, e faz um processamento para analisar cada uma delas;
- O aplicativo invoca via JNI funções da biblioteca FluidSynth que roda como código nativo;
- A biblioteca FluidSynth responde às chamadas e produz a síntese de áudio solicitada;
- A biblioteca retorna via JNI ao aplicativo que está rodando na JVM para continuar a execução do programa.
Para minimizar a latência e aumentar a performance das aplicações de áudio, foi criada a Native MIDI API do Android. Ela está disponível no Android NDK r20b e versões mais recentes. Ela permite que os desenvolvedores de aplicativos enviem e recebam dados MIDI com código C/C++ nativo.
Ao usar a Native MIDI API, deve-se transmitir o endereço de um MidiDevice para a camada de código nativo com uma chamada JNI. A partir daí, a API cria uma referência a um AMidiDevice, que tem a maior parte da funcionalidade de um MidiDevice. Seu código nativo usa funções da Native MIDI API que se comunicam diretamente com um AMidiDevice. O AMidiDevice se conecta diretamente ao serviço MIDI.
Fonte: https://developer.android.com/ndk/guides/audio/midi
Usando chamadas Native MIDI API, é possível integrar a lógica de áudio/controle C/C++ do aplicativo de forma mais próxima à transmissão MIDI. Há menos necessidade de chamadas JNI ou callbacks para a parte em Java/Kotlin do aplicativo. Por exemplo, um sintetizador de software implementado em código C (como o FluidSynth) poderia receber eventos do teclado controlador (MIDI) diretamente de AMidiDevice, em vez de esperar uma chamada de JNI para enviar os eventos da parte em Java/Kotlin. Ou, supondo que o aplicativo seja um controlador MIDI, um processo de composição algorítmica poderia ser escrito em C/C++, e enviar comandos MIDI diretamente a um AMidiDevice sem fazer callback para a parte em Java/Kotlin para transmitir os eventos ao sintetizador de hardware.
Embora a Native MIDI API melhore a conexão direta a dispositivos MIDI, os aplicativos ainda precisam usar o MidiManager para descobrir e abrir objetos MidiDevice. O AMidiDevice pode assumir a partir de então.
Às vezes, pode ser necessário passar informações da camada de interface de usuário (UI) para o código nativo. Por exemplo, quando eventos MIDI são enviados em resposta a botões na tela. Para isso, é necessário criar chamadas JNI personalizadas para a lógica nativa. Se for preciso enviar dados de volta para atualizar a UI, poderá ser feito um callback da camada nativa.
Maiores informações sobre a Android Native MIDI API podem ser encontradas na documentação oficial do Google Android Developers, disponível em: Android Native MIDI API Guide.
Latência de Som no Android
Latência é o tempo necessário para um sinal percorrer um sistema. Alguns tipos comuns de latência relacionados a aplicativos de áudio incluem:
- Latência na saída de áudio é o tempo entre a geração de uma amostra de áudio por um aplicativo e a reprodução da amostra na entrada para fone de ouvido ou alto-falante integrado.
- Latência na entrada de áudio é o tempo entre o recebimento de um sinal de áudio pela entrada de áudio de um dispositivo, como o microfone, e a disponibilização dos mesmos dados de áudio em um aplicativo.
- Latência de ida e volta é a soma da latência de entrada, do tempo de processamento do aplicativo e da latência de saída.
- Latência de toque é o tempo entre o toque de um usuário na tela e o recebimento desse evento de toque por um aplicativo.
- Latência de aquecimento é o tempo necessário para iniciar o canal do áudio na primeira vez em que os dados são colocados na fila de um buffer.
É difícil medir a latência na entrada e na saída de áudio de forma isolada, já que é preciso saber exatamente quando a primeira amostra foi enviada para o caminho de áudio (embora isso possa ser feito usando um circuito de testes e um osciloscópio). Se a latência de ida e volta do áudio é conhecida, é possível usar a seguinte regra geral: a latência na entrada (e na saída) de áudio é metade da latência de ida e volta em caminhos sem processamento de sinal.
A latência de ida e volta do áudio varia significativamente de acordo com o modelo do dispositivo e a versão de build do Android. É possível medir a latência de ida e volta do áudio criando um aplicativo que gere um sinal de áudio, acompanhando esse sinal e medindo o tempo entre o envio e o recebimento dele. A latência mais baixa é alcançada por caminhos de áudio com processamento mínimo de sinal.
Desta forma, é muito difícil especificar ou estabelecer com exatidão a latência para um aplicativo Android específico. No entanto, há algumas práticas que podem ajudar a obter a menor latência de áudio possível:
- Incluir no arquivo AndroidManifest.xml, uma das seguintes diretivas de uses-feature:
- <uses-feature android:name=”android.hardware.audio.low_latency” android:required=”true” />
Indica uma latência de saída contínua de 45 ms ou menos. - <uses-feature android:name=”android.hardware.audio.pro” android:required=”true” />
Indica uma latência de ida e volta contínua de 20 ms ou menos.
- <uses-feature android:name=”android.hardware.audio.low_latency” android:required=”true” />
- Usar a melhor taxa de amostragem (sample rate) e evitar conversões.
- Configurar o melhor tamanho para os buffers de áudio.
- Evite interfaces de saída (efeitos de áudio) que processam sinal e podem ser lentas.
Maiores informações sobre como reduzir a latência de áudio em aplicativos para Android podem ser encontradas na documentação oficial do Google Android Developers, disponível em: Android Audio Latency Guide.
Latência de Som no FluidSynth
A biblioteca FluidSynth também possui configurações que podem influenciar na latência de áudio do sintetizador, inclusive quando compilado para o Android:
- audio.oboe.performance-mode: [Apenas para Android] Configura o modo de performance como apontado pela documentação do Oboe (None, PowerSaving, LowLatency).
- audio.oboe.sharing-mode: [Apenas para Android] Configura o modo de compartilhamento como apontado pela documentação do Oboe (Shared, Exclusive).
- audio.periods: O número de buffers de áudio usados pelo driver. Esse número de buffers, multiplicado pelo tamanho do buffer, determina a latência máxima do driver de áudio (2 – 64, padrão: 16).
- audio.period-size: Este é o número de amostras de áudio que a maioria dos drivers de áudio solicitará do sintetizador de uma só vez. Em outras palavras, é a quantidade de amostras que o sintetizador tem permissão para renderizar de uma só vez quando nenhuma mudança de estado (eventos) está prestes a acontecer. Por isso, especificar números muito grandes aqui pode fazer com que os eventos MIDI sejam mal quantizados (sem tempo) quando um driver MIDI ou a API do sintetizador for usada diretamente, pois o FluidSynth não pode determinar quando esses eventos devem chegar (64 – 8192, padrão: 64).
- synth.cpu-cores: Define o número de núcleos de CPU para síntese. Se definido como um valor maior que 1, tarefas de síntese adicionais serão criadas para fazer o trabalho de renderização de áudio que é realizado. Isso tem o efeito de utilizar mais da CPU total para vozes, ou diminuir os tempos de renderização ao sintetizar áudio. Então, por exemplo, se for definido como 4, o FluidSynth tentará dividir o trabalho de síntese que ele precisa fazer entre uma thread de chamada do cliente e mais três threads de trabalho adicionais (internas). Assim que todas as threads tiverem terminado seu trabalho, seus resultados são coletados, e o buffer resultante é retornado ao chamador (1 – 256).
- synth.sample-rate: A taxa de amostragem do áudio gerado pelo sintetizador. Para desempenho ideal, certifique-se de que esse valor seja igual à taxa de saída nativa do driver de áudio. Alguns drivers, como o Oboe (Android), interpolarão as taxas de amostragem (8000 – 96000, padrão: 44100).
Projeto Prático: Um sintetizador MIDI como aplicativo para Android
Este artigo traz como exemplo para os conceitos apresentados um projeto prático: um aplicativo para Android de um sintetizador MIDI. O código-fonte completo deste projeto está disponível no repositório do GitHub, através da seguinte URL:
https://github.com/robsonsmartins/android-midi-synth
Licenciamento
O código-fonte deste projeto está licenciado sob a MIT License, que é uma licença para software livre e aberto: permite que o código seja utilizado para fins comerciais ou não-comerciais. Um cuidado especial porém está no licenciamento de alguns componentes de terceiros usados no projeto:
- A biblioteca FluidSynth: LGPL 2.1.
- O soundfont KawaiStereoGrand: CC0 1.0 Universal.
De qualquer maneira, ao reutilizar este código para qualquer propósito, convém mencionar os autores originais e creditá-los devidamente.
Caso o código-fonte da biblioteca FluidSynth seja alterado, também deverá ser disponibilizado o código-fonte do projeto, conforme estabelece a LGPL 2.1.
Passos para Compilar o Projeto
Para compilar e executar o projeto é necessário ter um ambiente configurado com o git, o Android Studio (incluindo o Java SDK, Android SDK e Android NDK) e o emulador Android (ou um smartphone ou tablet com modo desenvolvedor ativado). Isso poderá ser feito em uma máquina com sistema operacional Microsoft Windows® (instale o Git for Windows), GNU/Linux ou Apple macOS®.
- Clone o repositório do projeto, usando o comando git clone.
- Abra o projeto, apontando o diretório para o Android Studio.
- Aguarde o sincronismo do projeto feito pelo Gradle.
- Use o comando Build /Rebuild Project para compilar; ou selecione o “Running Device” e aperte o botão “Run app” para executar diretamente o aplicativo no emulador ou no smartphone selecionado.
Para testar o projeto, é necessário:
- Usar um smartphone ou tablet com o modo desenvolvedor ativado para transferir o aplicativo via Android Studio (pode ser usado o botão “Run app”).
- Conectar a saída de fone-de-ouvido do aparelho a uma caixa de som, ou alto-falante externo caso queira amplificar o som emitido.
- Conectar um cabo ou adaptador OTG a porta USB-C do aparelho, e em seguida conectar um cabo USB A-B ao teclado controlador MIDI (ou se ele tiver somente saídas MIDI com conectores DIN-5, usar um adaptador MIDI-USB conectado ao cabo OTG).
- Verificar se na tela do aplicativo aparece a mensagem de conexão do instrumento (device), e pressionar algumas teclas (notas musicais) e pedal de sustain. Os sons devem ser emitidos pelo aplicativo sintetizador, e na tela, devem ser exibidas quais teclas foram pressionadas (note on) e com qual intensidade (velocity), e quais foram soltas (note off).

Fonte: Autor
Detalhamento do Código-Fonte
O projeto de aplicativo Android do sintetizador MIDI está organizado da seguinte forma:
- assets – Nesta pasta se encontra o arquivo de soundfont que será reproduzido pelo sintetizador. Note que há um método implementado no código que copia o arquivo da pasta assets para uma pasta temporária, e então chama a função correspondente do FluidSynth para carregar o arquivo de soundfont.
- cpp – Nesta pasta está todo o código-fonte nativo em C++ da implementação do sintetizador e do gerenciador de mensagens MIDI, além dos arquivos de inclusão e as bibliotecas binárias compiladas em C do FluidSynth.
- java – Nesta pasta está todo o código-fonte do aplicativo, escrito em linguagem Kotlin. Além das classes que encapsulam as funções nativas (via JNI), há também uma classe do Activity principal do aplicativo (interface do usuário).
Arquivo de Manifesto
No arquivo AndroidManifest.xml estão as configurações básicas do aplicativo. As cláusulas que merecem destaque nesta aplicação são as de uses-feature:
- android.hardware.audio.pro – Indica que será usado o áudio de mais baixa latência possível no sistema Android, pois este é um aplicativo “profissional” de áudio.
- android.software.midi – Indica que serão usados os recursos de API MIDI neste aplicativo.
Biblioteca FluidSynth
Para embarcar a biblioteca FluidSynth no aplicativo, foi usada a versão pré-compilada para Android, disponível em FluidSynth Releases. A versão usada neste projeto foi a 2.4.0, lançada em 31 de outubro de 2024. Os passos sugeridos para fazer esse procedimento são descritos a seguir:
- Faça o download do arquivo zip contendo a release pré-compilada para Android;
- Descompacte o arquivo;
- Copie toda a pasta lib para o projeto, ficando no caminho <PROJECT_DIR>/app/src/main/cpp/fluidsynth/lib;
- Copie toda a pasta include para o projeto, ficando no caminho <PROJECT_DIR>/app/src/main/cpp/fluidsynth/include.
Todo esse procedimento, com detalhes, está descrito no documento oficial do FluidSynth: Using prebuilt libraries on Android.
Outra maneira para fazer isso é clonar o repositório do FluidSynth, e compilar o código-fonte para gerar os binários para Android, usando o NDK. Esse procedimento, com detalhes, está descrito no documento oficial do FluidSynth: Building For Android.
De qualquer maneira, para que o aplicativo utilize as bibliotecas nativas do FluidSynth, é preciso configurar corretamente o CMake, através do arquivo <PROJECT_DIR>/app/src/main/cpp/CMakeLists.txt.
Classes Nativas C++
Para manipular facilmente o código nativo, algumas classes foram criadas em C++ para esse propósito:
- SynthManager – Esta classe está implementada nos arquivos SynthManager.h e SynthManager.cpp. Ela é uma classe singleton, ou seja, ao longo da execução existe apenas uma instância dessa classe. O SynthManager é responsável por encapsular as chamadas ao FluidSynth, ou seja, essa classe é um wrapper que implementa um sintetizador via software.
- MidiManager – Esta classe está implementada nos arquivos MidiManager.h e MidiManager.cpp. Ela é uma classe singleton, ou seja, ao longo da execução existe apenas uma instância dessa classe. O MidiManager é responsável por implementar um processador de mensagens MIDI, e encapsular as chamadas de baixo nível do Android Native MIDI API. A instância dessa classe se comunica com a instância de SynthManager, a fim de reproduzir áudio sintetizado conforme a mensagem MIDI recebida e identificada. Para auxiliar na identificação das mensagens MIDI, um arquivo header MidiSpec.h foi adicionado, contendo uma lista com as mensagens mais comuns.
Interface JNI
Para promover a comunicação entre as instâncias das classes nativas em C++ e as classes que rodam na JVM (Kotlin), é necessário construir uma implementação JNI. No caso deste projeto, as declarações JNI foram adicionadas ao final dos arquivos SynthManager.cpp e MidiManager.cpp. Usando a cláusula extern “C”, as funções declaradas pelo JNI (puro C) fazem um wrapper para as instâncias e métodos das duas classes C++ correspondentes. Desta forma, o código C++ pode se compatibilizar ao modelo do JNI, que é puro C (apenas funções).
Classes Kotlin
Para se comunicar com o código nativo via JNI, foram implementadas as seguintes classes em Kotlin:
- SynthManager – Esta classe está implementada no arquivo SynthManager.kt. Ela encapsula todas as chamadas de JNI à classe de mesmo nome do código nativo. O SynthManager é responsável por encapsular as chamadas ao FluidSynth, ou seja, essa classe é um wrapper que implementa um sintetizador via software. Nota: Nem todos os métodos foram implementados nesta classe porque o aplicativo não necessita de todos eles.
- MidiManager – Esta classe está implementada no arquivo MidiManager.kt. Ela encapsula todas as chamadas de JNI à classe de mesmo nome do código nativo, além de prover a busca por dispositivos MIDI conectados no Android (o dispositivo conectado é aberto, e uma referência ao objeto criado é passada para as chamadas de código nativo). Além disso, há a implementação de um callback para receber mensagens a partir do código nativo, que serão exibidas na interface do usuário. O MidiManager é responsável por implementar um processador de mensagens MIDI, e encapsular as chamadas de baixo nível do Android Native MIDI API.
Além dessas classes, também há a implementação em Kotlin da Activity principal do aplicativo (para prover a interface de usuário):
- MainActivity – Esta classe está implementada no arquivo MainActivity.kt. Ela implementa o Activity principal, ou seja, a interface de usuário, além de fazer chamadas às outras classes Kotlin (cria uma instância de SynthManager, outra de MidiManager e configura tudo o que é necessário, além de inicializar a biblioteca nativa).
Conclusão
Este artigo apresentou uma visão geral sobre o desenvolvimento de um aplicativo sintetizador MIDI para Android, combinando o uso de linguagens Kotlin, C, C++ e da biblioteca FluidSynth. Foram detalhados os principais conceitos e tecnologias necessários para implementar uma solução funcional, desde os fundamentos do protocolo MIDI até a integração de soundfonts para a reprodução de sons.
A arquitetura Android, com suporte à execução nativa por meio da JNI e ao processamento de áudio em tempo real, demonstrou ser uma plataforma interessante para este tipo de aplicação. A combinação de APIs MIDI, configurações otimizadas para baixa latência e a flexibilidade da biblioteca FluidSynth possibilitaram a criação prática de um projeto exemplo, que foi demonstrado tanto em seus aspectos técnicos como quanto aos desafios inerentes à abordagem utilizada.
Ao longo do texto, também foram discutidas as vantagens e limitações das ferramentas utilizadas, assim como as configurações e etapas necessárias para preparar o ambiente de desenvolvimento e compilar o aplicativo. O exemplo prático fornecido ilustra de maneira clara como conectar controladores MIDI a dispositivos Android, integrar bibliotecas nativas e utilizar soundfonts para síntese sonora.
O projeto disponibilizado na íntegra no repositório GitHub serve como um ponto de partida para desenvolvedores interessados em explorar o potencial do Android para aplicações de áudio e música digital. Com isso, espera-se que o material apresentado neste artigo forneça não apenas conhecimento técnico, mas também inspiração para novas implementações e inovações no campo musical.
Referências
FluidSynth. A SoundFont Synthesizer. 2017. Disponível em: https://www.fluidsynth.org/. Acesso em: 14 de dez. 2024.
FluidSynth. FluidSynth 2.4 Developer Documentation. 2024. Disponível em: https://www.fluidsynth.org/api/. Acesso em: 14 de dez. 2024.
FluidSynth. FluidSynth GitHub Repository. 2017. Disponível em: https://github.com/FluidSynth/fluidsynth. Acesso em: 14 de dez. 2024.
FluidSynth. Wiki: Building For Android. 2023. Disponível em: https://github.com/FluidSynth/fluidsynth/wiki/BuildingForAndroid. Acesso em: 14 de dez. 2024.
FluidSynth. Wiki: Fluid Features. 2023. Disponível em: https://github.com/FluidSynth/fluidsynth/wiki/FluidFeatures. Acesso em: 14 de dez. 2024.
FluidSynth. Wiki: Using prebuilt libraries on Android. 2023. Disponível em: https://github.com/FluidSynth/fluidsynth/wiki/Using-prebuilt-libraries-on-Android. Acesso em: 14 de dez. 2024.
Google. Android Developers. 2008. Disponível em: https://developer.android.com. Acesso em: 14 de dez. 2024.
Google. Android Developers: Audio Latency Guide. 2015. Disponível em: https://developer.android.com/ndk/guides/audio/audio-latency. Acesso em: 14 de dez. 2024.
Google. Android Developers: Native MIDI API Guide. 2019. Disponível em: https://developer.android.com/ndk/guides/audio/midi. Acesso em: 14 de dez. 2024.
Google. Android Developers: Native MIDI sample. 2019. Disponível em: https://github.com/android/ndk-samples/tree/main/native-midi. Acesso em: 14 de dez. 2024.
Google. Android Developers: Platform architecture. 2024. Disponível em: https://developer.android.com/guide/platform. Acesso em: 14 de dez. 2024.
Google. Android Open Source Project. 2008. Disponível em: https://source.android.com. Acesso em: 14 de dez. 2024.
Martins, R. Android MIDI Synth Project. 2024. Disponível em: https://github.com/robsonsmartins/android-midi-synth. Acesso em: 14 de dez. 2024.
Martins, R. MIDI – PC Adapter. 2008. Disponível em: https://robsonmartins.com/content/eletr/projects/midi/. Acesso em: 14 de dez. 2024.
Martins, R. MIDI – USB Adapter. 2012. Disponível em: https://robsonmartins.com/content/eletr/projects/midiusb/. Acesso em: 14 de dez. 2024.
Polyphone. Polyphone SoundFont Editor. 2013. Disponível em: https://www.polyphone.io/. Acesso em: 14 de dez. 2024.
Polyphone. Polyphone soundfonts. 2013. Disponível em: https://www.polyphone.io/en/soundfonts. Acesso em: 14 de dez. 2024.
Polyphone. The different soundfont formats. 2013. Disponível em: https://www.polyphone.io/en/documentation/manual/annexes/the-different-soundfont-formats. Acesso em: 14 de dez. 2024.
Ricardo, H. Creating a Fluidsynth Hello World App for Android. 2020. Disponível em: https://medium.com/swlh/creating-a-fluidsynth-hello-world-app-for-android-5e112454a8eb. Acesso em: 14 de dez. 2024.
SFZ Format. SFZ Format Overview. 2020. Disponível em: https://sfzformat.com/. Acesso em: 14 de dez. 2024.
The MIDI Association. MIDI Specifications. 2024. Disponível em: https://midi.org/specs. Acesso em: 14 de dez. 2024.
Imagem de Destaque: Gerada por IA.





