Após um pequeno período de descanso desde o último post, retorno finalmente para a conclusão da série que descreve como converter um arquivo MIDI para o MSX, bem como técnicas utilizadas tocar musica no MSX em geral.
Quando iniciei a pesquisa para converter MIDI para o MSX, eu esperava conseguir ferramentas 100% prontas para executar essa tarefa, porém após conseguir o 3MLEditor e o MusicA, eu já estava conformado de que teria que criar pelo menos uma ferramenta para auxiliar no processo de conversão, o que realmente aconteceu.
Como já vimos nos últimos posts, conseguimos converter um arquivo MIDI para um arquivo MML proprietário gerado pelo 3MLEditor e também conhecemos o formato MSD, que é o arquivo MML aceito pelo MusicA, que por sua vez converte o MSD para o formato aceito por muitos replayers de MSX, o BGM (Background Music).
Mas ficou um buraco no processo, entre gerar o MML pelo 3MLEditor e transportá-lo ao MusicA, e é justamente aí que entra um componente importante no processo de conversão, que é o mml2msd compiler.
mml2msd Compiler
O mml2msd compiler foi criado justamente para compilar o MML gerado no formato proprietário do 3MLEditor, convertendo-o para o formato MSD aceito pelo MusicA, inclusive aplicando todos os filtros necessários para que os dados MML gerados sejam compatíveis com o MusicA, pois conforme eu escrevi no segundo artigo da série, o MML não é um padrão de facto, logo, existem diferenças no MML entre programas e plataformas diferentes.
Voltando ao mml2msd compiler, essa ferramenta é um compilador de linha de comando criada em C++ ANSI, sendo seu código compilável em qualquer sistema que disponha de um compilador C++ ANSI compatível, o que a torna disponível para praticamente qualquer sistema operacional e plataforma existente na atualidade, como MacOSX, Linux, Windows, Solaris, *BSD (FreeBSD, NetBSD e OpenBSD), dentre outras.
Tecnologia
A ferramenta foi construída com técnicas de orientação a objetos, que permitem sua expansão de maneira simples e de forma que possamos reutilizar grande parte de seu código para criação de novas ferramentas no futuro.
Após o término dessa ferramenta, chegamos a um engine de conversão, cujo processo está dividido em 3 partes:
- MML Parser;
- MML Compiler;
- MML Linker;
.
MML Parser & MML Compiler
O MML parser é o responsável por identificar cada seção do arquivo MML de entrada e Tokenizar essa informação em uma arvore em memória e para isso utilizei o uso extensivo de containers STL (Standard Template Library) de forma que os dados recebidos do parser são pré-processados e compilados nesses containers, ficando organizados e prontos para o processo de linkagem no formato do objeto final.
Algumas tarefas de pré-processamento são feitas durante o parsing do MML de entrada, como por exemplo ignorar comentário de uma linha ( // ) ou de multiplas colunas ( /* */ ). O MML do 3MLEditor não suporta comentários de multiplas linhas.
Como o formato MML do 3MLEditor é bem simples e geralmente é composto por seções e dados que podem organizados através do pair <Key, Value>, então não foi complicado explicitar isso utilizando as seguintes estruturas em C++ (STL), que definem exatamente os canais e seu código MML associado, bem como algumas estruturas com features adicionais do compilador, como por exemplo a possibilidade do usuário adicionar seu próprio código MML customizado no objeto convertido final.
C++ source code
/** * Token types enumeration. */ enum TokenType { TOKEN_TYPE_UNKNOWN = 0, TOKEN_TYPE_SECTION, TOKEN_TYPE_DATA, TOKEN_TYPE_BLANK, TOKEN_TYPE_ONE_LINE_REMARKS, TOKEN_TYPE_MULTI_LINES_REMARKS, TOKEN_TYPE_OPEN_MULTI_LINES_REMARKS, TOKEN_TYPE_CLOSE_MULTI_LINES_REMARKS };/** * Data types enumeration. */ enum TokenDataType { DATA_TYPE_UNKNOWN = 0, DATA_TYPE_KEY_VALUE, DATA_TYPE_VALUE };/** * New definitions types for use with framework. */ struct stToken { TokenDataType type; string strKey; string strValue; };typedef set<int> ChannelSet; typedef map<int, string> ChannelMap; typedef map<stToken*, ChannelSet*> CustomMMLMap; typedef vector<stToken*> TokenVector; typedef map<stToken*, TokenVector*> TokenVectorMap;
O processo de conversão em nenhum momento, seja no parsing e compilação das informações ou na linkagem, não requer nenhuma técnica de recursividade, principalmente pelo formato de entrada (MML) ser simples. Entretanto o objeto final (MSD) é um pouco mais complicado, uma vez que é um formato baseado em duas seções, sendo a primeira a de definições da estrutura dos canais físicos de som e ordem de execução dos comandos MML e a segunda que descreve o conteúdo dos comandos MML que serão executados pelos canais físicos.
MML Linker
Uma vez com toda a árvore de comandos <canais/comandos> MML carregados em memória, nos resta apenas transferir seu conteúdo para o objeto destino, no caso um arquivo no formato MSD, aceito pelo MusicA.
O processo consiste em uma montagem de dois passos.
- Associação dos canais físicos da máquina (FM, PSG e SCC) com os lógicos (conteúdo MML);
- Associação dos canais lógicos ao seu respectivo conteúdo MML;
.
O que resulta no seguinte arquivo MSD, conforme exemplo abaixo:
MusicA MSD Format
; MSD file generated by mml2msd compiler.
; CopyLeft (c) since 2011 by PlanetaMessenger.org.
;;
; Associação dos canais físicos aos lógicos
;FM1=C1,CH1
FM2=C1,CH2
FM3=C1,CH3
FM4=
FM5=
FM6=
FMR=
FM7=
FM8=
FM9=
PSG1=
PSG2=
PSG3=
SCC1=
SCC2=
SCC3=
SCC4=
SCC5=;
; Associação dos canais lógicos aos dados MML
;C1=T200
CH1=T200V8>E16G+16C+16E16V10<A16>C+16V14<G+16>C+16<A16F+16E16A16>E16G+16C+16E16V10<A16>C+16V14<G+16>C+16<A16F+16E16A16R8A.
CH2=V14R1.R32C-16F+.F+32
CH3=V14R1.<F+16>D.D16
Conforme citado anteriormente, o conteúdo MML aceito pelo MusicA não é 100% compatível com com gerado pelo 3MLEditor e também com o do MSX-BASIC, por isso durante esse processo de linkagem é feito um pós-processamento no conteúdo gerado para compatibilizar o MML gerado.
Filtros
O MusicA não suporta algumas sequências de MML que geralmente são aceitas pelo 3MLEditor e também pelo MSX-BASIC, sendo algumas dessas sequencias inexistentes no MusicA e outras que considerando um pouco de teoria de musical, podem ser substituídas por uma sequência que as torna musicalmente compatível.
O processo de busca de tais sequências no código MML é na maioria das vezes complexo e suscetível a erros, podendo resultar em funcionamento indesejado do software bem como geração de dados indesejáveis ao formato final, por isso considerei bastante a utilização de expressões regulares durante o desenvolvimeto desse software.
O Regex, ou regular expressions, é uma tecnologia perfeita a ser utilizada no processo de busca e substituição de sequências MML inválidas e existem diversas bibliotecas feitas em C++ que implementam o engine para processamento de expressões regulares e a mais famosa delas é a Boost.
Boost é uma biblioteca escrita em C++ e que contempla uma infinidade de novos containers e algorítmos desenvolvidos com base na idéia de Alexander Stepanov e sua STL. O pacote regex da Boost é perfeito para o trabalho de busca e substituição que necessitamos, porém eu iniciei o desenvolvimento desse software utilizando a ferramenta BloodShed DevC++, que é uma IDE de código fonte aberto, para Windows, e que usa o compilador gcc via MinGW, mas ao tentar ajustar essa ferramenta para utilização do Boost percebi que, assim como eu, muitas pessoas tem dificuldades de integração dessa biblioteca com essa IDE. Na verdade a grande maioria da Boost é composta apenas de templates escritas apenas nos headers (.h) da biblioteca e se você tentar utilizar esses templates no DevC++, não terá problemas, porém o regex é um dos poucos módulos da Boost que tem código fonte (.cpp) fora dos headers e com isso que necessta que o host compiler seja configurado para incluir referência a biblioteca regex (.lib) do Boost.
Li em alguns foruns, que algumas pessoas conseguiram trabalhar com Boost no DevC++, porém decidi não gastar muito tempo com isso e temporariamente decidi não utilizar regex nem o Boost e vou deixar essa melhoria no código para uma futura versão do mml2msd, com isso fiz minhas rotinas de busca e substituição todas baseadas na STL mesmo, basicamente utilizando std::string.
Abaixo, uma lista do que não é suportado e o que foi subsitituído por uma sequencia compatível.
C++ Source Code
/** * Apply filter to Ampersand (&) character. * & after numbers is not possible; * & after dot (.) is not possible; * @param strToken The data to apply filter; */ void ThreeML :: AmpersandFilter( string &strToken ); /** * Combine two quarter notes together. * @param strToken The MML data to filter; * Thanks to The Ultimate PPMCK MML reference. * http://woolyss.com/chipmusic/chipmusic-mml/ppmck_guide.php * and to SiOPMML reference. * http://mmltalks.appspot.com/document/siopm_mml_ref_05_e.html */ void ThreeML :: CombineQuarterNotesFilter( string &strToken ); /** * Apply filter to Less-than (<) character. * Two << cannot be in sequence; * @param strToken The data to apply filter; */ void ThreeML :: OctaveLowerFilter( string &strToken ); /** * Apply filter to Dot (.) character. * . after L(n) is not possible; * @param strToken The data to apply filter; */ void ThreeML :: DotFilter( string &strToken ); /** * Apply filter to Tilde (~) character, removing it; * @param strToken The data to apply filter; */ void ThreeML :: TildeFilter( string &strToken ); /** * Filter for invalid volume MML tag. Some MML sequences * is higher than MSD (MusicA) supports, then this will * be updated for maximum value accepted by MSD format (15). * @param strToken The data to apply filter; */ void ThreeML :: InvalidVolumeFilter( string &strToken ); /** * Filter all instruments of token, removing it from * sequence. * @param strToken The data to apply filter; */ void ThreeML :: InstrumentsFilter( string &strToken );
Ainda estou estudando algumas possibilidades em cima desses filtros, como por exemplo o OctaveLowerFilter, pois acredito que os casos de MML inválidos que peguei talvez sejam porque, nesses casos, o MML original esteja fazendo uma tentativa de retornar 1 oitava que esteja fora da faixa do teclado virtual do MSX ou apenas do MusicA, por isso o mml2msd ainda está em desenvolvimento e ainda teremos algumas versões do mesmo pelos próximos meses, para afinar esses filtros do compilador.
Limitações da primeira versão e melhorias futuras.
A maior limitação do mm2msd é que ainda não estou tratando os canais de percussão, ou seja, ainda não temos uma conversão com o devido tratamento para os canais de bateria e ritmo. Se fosse uma conversão de MML para MSX-BASIC, seria mais tranquilo pois a única conversão necessária seria criar uma tabela de conversão dos instrumentos aceitos pelo MIDI para os aceitos pelo FM do MSX, entretando em se tratando de MusicA, a maneira como ele trata o ritmo é algo particular e que ainda estou mapeando para ajustar o compilador de forma que o mesmo possa transformar essa infomação, possibilitando assim que o MusicA execute os canais de ritmo corretamente, então, posso dizer que já está na lista de melhorias como prioridade.
Outra melhoria que pretendo liberar na próxima versão é a possibilidade de se incrementar o tempo utilizando um incremento especificado pelo usuário, com unidade em BPM (Batidas Por Minuto), ou seja, se uma música tem originalmente o tempo de 20BPM, poderemos desejar que o tempo da música, após a conversão, sejá de 80BPM por exemplo, para isso será necessário especificar, no momento da conversão, o valor a ser incrementado, no caso 60BPM, ao tempo, então a música convertida será executada nesse novo tempo. Isso é muito importante pois existem músicas em que determinados canais estão setados com um tempo X e outros com um tempo Y, ou seja, se tentarmos aumentar ou diminuir a velocidade da música por um processo de conversão manual no MML original, estamos propensos a inserir erros na execução da música.
mm2msd in action
Como já foi descrito anteriormente, o mml2msd é uma ferramenta de linha de comando, portanto em ambiente Windows poderá ser utilizada via cmd e nos UNIX, em geral, através do console comum a esses sistemas.
Segue abaixo tela de ajuda (help) do mml2msd, que mostra todas as opções do compilador. Essa tela de ajuda aparece caso o usuário chame o mml2msd sem parâmetro algum, ou passando a opção -h (mml2msd -h).
mml2msd MML Compiler. CopyLeft (c) since 2011 by PlanetaMessenger.org Vrs. 0.0
Usage mml2msd [-ft][-v][-ac][-acf filename][-of filename][-ri][-h] input_file_nameOptional parameters:-ft Try to find the tempo on source MML and add it to all channels on destination file.-v Turn on verbose mode.-ac Add custom MML to beginning of all channels on destination file.
-acf Add custom MML to beginning of all channels based on content of file specified by parameter filename. See about how to make a custom MML file.
-of Output to file specified by filename.
-ri Generate destination file without the instruments of source MML.
-rc Remove generated comments on destination MML.
-h Show this help screen. How to make a custom MML file.If you want to use a custom MML file to each channel of source that you’re converting, just create a file with the following format:
FM1=<Custom_MML_command> FM2=<Custom_MML_command> FM3=<Custom_MML_command> FM4=<Custom_MML_command> FM5=<Custom_MML_command> FM6=<Custom_MML_command> FMR=<Custom_MML_command> FM8=<Custom_MML_command> FM9=<Custom_MML_command> PSG1=<Custom_MML_command> PSG2=<Custom_MML_command> PSG3=<Custom_MML_command> SCC1=<Custom_MML_command> SCC2=<Custom_MML_command> SCC3=<Custom_MML_command> SCC4=<Custom_MML_command> SCC5=<Custom_MML_command> Where <Custom_MML_command>is the MML that you want add to beginning of each specified channel.mml2msd was developed by PopolonY2k for PlanetaMessenger.org.For new versions please visit http://sourceforge.net/projects/oldskooltech/
Software information at http://www.popolony2k.com.br
Project home at http://www.planetamessenger.org
A forma de operação mais simplista do mml2msd é chamá-lo da seguinte forma pela linha de comando:
popolony2k@ZanacEx:~$ mml2msd nome_arq.mml
No exemplo acima o compilador abre o arquivo .mml passado por parâmetro gerando um arquivo de saída com o mesmo nome do arquivo de entrada acrescido da extensão .msd.
Operando na forma minimalista, o mml2msd tentará converter o MML da forma mais fiel possível, entretanto o MusicA tem algumas diferenças, principalmente relativas ao tempo da melodia, e por isso existem parâmetros adicionais no mml2msd que possibilitam controlar algumas opções na geração do arquivo final de maneira mais avançada.
Segue abaixo a descrição dos parâmetros opcionais do mml2msd (opção -h):
[-ft] Try to find the tempo on source MML and add it to all channels on destination file.
Essa opção tenta encontrar o tempo a ser utilizado por todos os canais da música convertida. É importante pois no MusicA cada canal deve ter seu tempo configurado, o que não acontece no MML original, geralmente esse tempo é o mesmo do primeiro canal da melodia.
[-v] Turn on verbose mode.
Liga o modo verboso, ou seja, todos os prints do sistema estão ativados com essa opção. Ótima para debug.
[-ac] Add custom MML to beginning of all channels on destination file.
Permite que a adição de comando MML customizados, no inicio de todos os canais.
Ex: mml2msd -ac v10 arq_mml.mml
Ótimo para configuração dos canais, quando todos compartilham a mesma configuração, por exemplo volume ou tempo.
[-acf] Add custom MML to beginning of all channels based on content of file specified by parameter filename.
Assim como a opção -ac acima, a opção -acf permite adição de código MML customizado no inicio dos canais, porém essa opção permite que essa tarefa seja feita a partir de um arquivo de entrada.
Ex: mml2msd -acf arquivo_entrada arq_mml.mml
O arquivo que contém o código MML de entrada (arquivo_entrada) deve ser um arquivo texto no seguinte formato:
FM1=<Custom_MML_command>
FM2=<Custom_MML_command>
FM3=<Custom_MML_command>
FM4=<Custom_MML_command>
FM5=<Custom_MML_command>
FM6=<Custom_MML_command>
FMR=<Custom_MML_command>
FM8=<Custom_MML_command>
FM9=<Custom_MML_command>
PSG1=<Custom_MML_command>
PSG2=<Custom_MML_command>
PSG3=<Custom_MML_command>
SCC1=<Custom_MML_command>
SCC2=<Custom_MML_command>
SCC3=<Custom_MML_command>
SCC4=<Custom_MML_command>
SCC5=<Custom_MML_command>
Onde cada um dos canais físicos (FM1…FM<n>, SCC1…SCC<n>, PSG1…PSG<n>) poderá ter um código MML associado que será adicionado no inicio de cada canal especificado no arquivo MSD final.
[-of] Output to file specified by filename.
Especifica o nome do arquivo de saída, permitindo assim que o nome do mesmo seja diferente do arquivo de entrada.
[-ri] Generate destination file without the instruments of source MML.
Permite que se retire os instrumentos do arquivo final, ou seja, ao utilizar essa opção a melodia ficará com os instrumentos default para todos os canais. Essa opção tem alguma utilidade na versão 0.0 do mml2msd pois essa versão ainda não possui tabela de conversão de instrumentos, logo, os instrumentos do MIDI original poderão não ser os mesmos quando transportados para o MSX.
[-rc] Remove generated comments on destination MML.
O mml2msd adiciona, por default, comentários entre seções dentro do MSD gerado, para desabilitar essa feature chame o compilador com essa opção ativada.
[-h] Show this help screen
Mostra a tela de ajuda com as opções acima.
Código fonte
O código do mml2msd já está liberado sob licença GPLv3 em sua versão 0.0, no repositório do Old Skool Tech, com os seguintes endereços para download:
.
A versão em desenvolvimento está disponível no SVN do projeto e daqui 1 ou 2 meses teremos novas features adicionadas a essa ferramenta bem como a construção de uma outra que é o mml2basic, que transformará o MML do 3MLEditor para código MSX-BASIC, prontinho para executar no MSX.
Também na versão que está no SVN já existe um makefile (Makefile.unix) que possibilita a compilação e geração de binários do mml2msd, para as plataformas UNIX em geral, logo já é possível utilizar o software nos Linux, *BSD, Solaris, etc. A partir da próxima versão vou liberar uma distribuição de binário da ferramenta para o sistema operacional Linux, na área de download do projeto.
Conclusão.
Finalmente chego ao final dessa série em que o resultado foi muito gratificante de escrever e compartilhar, principalmente depois de todas essas pesquisas feitas desde o final do ano passado e principalmente depois de perceber que temos muito que fazer no MSX, que tem um terreno fértil e muitas vezes inexplorado no quesito software.
Infelizmente não temos muita gente na ativa desenvolvendo softwares para MSX, máquina essa que acho excelente para aprender ainda nos dias atuais, principalmente para quem deseja se aprofundar na área de software para sistemas embarcados, onde o MSX compartilha muita coisa com os sistemas embarcados atuais. Então quem está no mundo MSX pode fazer a diferença e por isso brevemente pretendo continuar o desenvolvimento de softwares musicais para MSX e minhas pretensões se expandem para a construção de um Player para PC que seja capaz de converter MIDI diretamente para MSD ou até mesmo BGM e também um replayer para MSX que suporte além de FM, PSG e SCC, também a opção MoonSound.
Ou seja, temos bastante coisa para aprender e compartilhar.
Um abraço a todos e fiquem antenados nesse blog pois vem mais coisa pela frente…..temos um ano inteiro para desbravar o universo MSX.
[]’s
PopolonY2k
Todos os 4 artigos da série MusicA – Tocando MIDI no MSX são dedicados a Mãe e Professora de Musica Lêda Campos Ferreira.
Referência na internet
MusicA – Tocando MIDI no MSX – Parte II
http://www.popolony2k.com.br/?p=742
MusicA – Tocando MIDI no MSX – Parte III
http://www.popolony2k.com.br/?p=808
The Ultimate PPMCK MML reference
http://woolyss.com/chipmusic/chipmusic-mml/ppmck_guide.php
SiOPMML reference
http://mmltalks.appspot.com/document/siopm_mml_ref_05_e.html
Old Skool Tech – Old School Technology
http://sourceforge.net/projects/oldskooltech/
Regular Expressions (Regex)
http://en.wikipedia.org/wiki/Regular_expression
Standard Template Library (STL)
http://en.wikipedia.org/wiki/Standard_Template_Library
Boost C++ Libraries
http://www.boost.org/
Minimalist GNU for Windows (MinGW)
http://www.mingw.org/Windows
GCC – The GNU Compiler Collection
http://gcc.gnu.org/
Alexander Stepanov
http://en.wikipedia.org/wiki/Alexander_Stepanov