
Em um
artigo anterior, descrevi a linha de raciocínio ao analisar um formato de dados binários desconhecido. Usando o editor hexadecimal Synalaze It!, Mostrei como você pode analisar o cabeçalho de um arquivo binário e destacar os principais blocos de dados. Como no caso do formato SNG, esses blocos formam uma estrutura hierárquica, consegui usar a recursão na gramática para criar automaticamente sua visualização em árvore de forma legível por humanos.
Neste artigo, descreverei uma abordagem semelhante usada para analisar diretamente os dados da música. Usando os recursos internos do editor Hex, criarei um protótipo de um conversor de dados no formato Midi comum e simples. Teremos que enfrentar várias armadilhas e resolver a tarefa aparentemente simples de converter amostras de tempo. Por fim, explicarei como você pode usar os resultados obtidos e a gramática do arquivo binário para gerar parte do código para o futuro conversor.
Analisando dados de música
Então, é hora de descobrir como os dados da música são armazenados em arquivos .SNG. Em parte, mencionei isso em um artigo anterior. A documentação do sintetizador afirma que o arquivo SNG pode conter até 128 "músicas", cada uma composta por 16 faixas e uma faixa principal (para gravar eventos globais e alterar os efeitos principais). Diferentemente do formato Midi, onde os eventos de música simplesmente se seguem com um delta de tempo específico, o formato SNG contém medidas de música.
Uma medida é um tipo de contêiner para uma sequência de notas. A dimensão da medida é indicada em notação musical. Por exemplo, 4/4 - significa que a medida contém 4 batidas, cada uma com duração igual a uma semínima. Simplificando, essa medida conterá quatro semínimas, ou duas semitons ou oito colcheias.
Veja como fica na notação musical As medidas no arquivo SNG são usadas para editar faixas no seqüenciador de sintetizador incorporado. Usando o menu, você pode excluir, adicionar e duplicar medidas em qualquer lugar da faixa. Você também pode repetir ciclos ou alterar suas dimensões. Finalmente, você pode simplesmente começar a gravar uma faixa a partir de qualquer medida.
Vamos tentar ver como tudo isso é armazenado em um arquivo binário. O contêiner comum para "músicas" é o bloco SGS1. Os dados para cada música são armazenados nos blocos SDT1:

Os blocos SPR1 e BMT1 armazenam configurações gerais da música (andamento, configurações do metrônomo) e configurações individuais da faixa (patches, efeitos, configurações do arpejador, etc.). Estamos interessados no bloco TRK1 - ele contém eventos de música. Mas você precisa descer mais alguns níveis da hierarquia - para bloquear o MTK1

Finalmente, encontramos nossas faixas - esses são os blocos MTE1. Vamos tentar gravar uma faixa vazia de curta duração no sintetizador e mais um pouco - para entender como as informações sobre medidas em forma binária são armazenadas.

Parece que as medidas são armazenadas como estruturas de oito bytes. Adicione algumas notas:

Portanto, podemos assumir que todos os eventos são armazenados da mesma forma. O início do bloco MTE contém informações ainda desconhecidas e, em seguida, a sequência de estruturas de oito bytes chega ao fim. Abra o editor de gramática e crie uma estrutura de
eventos com um tamanho de 8 bytes.
Adicione a estrutura
mte1Chunk que herda
childChunk e coloque um link para o
evento na estrutura de
dados . Indicamos que o
evento pode ser repetido um número ilimitado de vezes. Em seguida, através de experimentos, descobrimos o tamanho e o objetivo de vários bytes antes do início do fluxo de eventos da trilha. Eu tenho o seguinte:

No início do bloco MTE1, o número de eventos da faixa, seu número e, presumivelmente, a dimensão do evento são armazenados. Depois de aplicar a gramática, o bloco começou a ficar assim:

Vamos seguir para o fluxo de eventos. Depois de analisar vários arquivos com diferentes seqüências de notas, a seguinte imagem aparece:
# | Tipo | Representação binária |
---|
1 | Beat 1 | 01 00 00 ... |
2 | Nota | 09 00 3C ... |
3 | Nota | 09 00 3C ... |
4 | Nota | 09 00 3C ... |
5 | Beat2 | 01 C3 90 ... |
6 | Nota | 09 00 3C ... |
7 | Fim da trilha | 03 88 70 ... |
Parece que o primeiro byte codifica o tipo de evento. Adicione um campo de
tipo à estrutura do
evento . Vamos criar mais duas estruturas que herdam o
evento :
medir e
anotar . Indicamos os valores fixos correspondentes para cada um deles. E, finalmente, adicione links para essas estruturas nos
dados do bloco
mte1Chunk .

Aplique as alterações:

Bem, fizemos um bom progresso. Resta entender como a altura e a força da nota são codificadas, bem como a mudança de horário de cada evento em relação aos outros. Vamos tentar novamente comparar nossos arquivos com o resultado da exportação para o midi, feito através do menu do sintetizador. Desta vez, estamos especificamente interessados nos eventos de clicar em notas.

Os mesmos eventos no arquivo SNG Ótimo! Parece que o tom e a pressão das notas são codificados exatamente da mesma maneira que no formato midi, com apenas alguns bytes. Adicione os campos apropriados à gramática.
Infelizmente, as coisas não são tão simples com uma mudança temporária.
Lidamos com a duração e delta
No formato midi, os eventos NoteOn e NoteOff são separados. A duração de uma nota é determinada pelo tempo delta entre esses eventos. No caso do formato SNG, onde não há análogo do evento NoteOff, os valores delta de duração e tempo devem ser armazenados em uma estrutura.
Para entender como eles são armazenados, gravei várias seqüências de notas de diferentes durações no sintetizador.

Obviamente, os dados que precisamos estão nos últimos 4 bytes da estrutura de eventos. A regularidade não é visível a olho nu, portanto, selecionamos os bytes de interesse para nós no editor e usamos a ferramenta Painel de Dados.
Aparentemente, a duração da nota e o deslocamento no tempo são codificados por um par de bytes (UInt16). Nesse caso, a ordem dos bytes é inversa - Little Endian. Tendo comparado uma quantidade suficiente de dados, descobri que o delta de tempo aqui não é contado a partir do evento anterior, como no midi, mas desde o início do relógio. Se a nota terminar no próximo compasso, no compasso atual seu comprimento será 0x7fff e no próximo será repetido com o mesmo delta 0x7fff e a duração contada em relação ao início de um novo compasso. Da mesma forma, se uma nota emitir várias medidas, em cada uma delas a duração e o delta serão iguais a 0x7fff.
Circuito pequeno
As unidades de tempo delta / duração são contadas em células. A nota 1 soa normal e a nota 2 continua a soar nos segundo e terceiro compassos. Na minha opinião, tudo isso parece um pouco intrometido. Por outro lado, na notação musical, as notas que tocam continuamente várias medidas são indicadas de maneira semelhante pelo legato.
Em quais "papagaios" temos uma duração? Como midi, os tiques são usados aqui. A partir da documentação, sabe-se que a duração de um compartilhamento é de 480 ticks. Com um andamento de 100 batidas por minuto e uma dimensão 4/4, a duração da semínima é (60/100) = 0,6 segundos. Consequentemente, a duração de um tick é de 0,6 / 480 = 0,00125 segundos. Uma batida 4/4 padrão dura 4 * 480 = 1920 ticks ou 2,4 segundos a uma taxa de 100 bpm.
Tudo isso será útil para nós no futuro. Enquanto isso, adicione a duração e o delta à nossa estrutura de
notas . Além disso, observe que há um campo na estrutura do tato que armazena o número de eventos. Outro campo contém o número de série da medida - adicione-os à estrutura da
medida .

Protótipo do conversor
Agora temos informações suficientes para tentar converter os dados. O editor Hex Synalaze It na versão pro permite que você escreva scripts em python ou lua. Ao criar um script, você precisa decidir com o que queremos trabalhar: com a própria gramática, com arquivos individuais no disco ou de alguma forma processar os dados analisados. Infelizmente, cada um dos modelos tem algumas limitações. O programa fornece várias classes e métodos para trabalhar, mas nem todos são acessíveis a partir de todos os modelos. Talvez seja uma falha na documentação, mas não encontrei como você pode carregar a gramática de uma lista de arquivos, analisá-los e usar as estruturas resultantes para exportar dados.
Portanto, criaremos um script para trabalhar com o resultado da análise do arquivo atual. Este modelo implementa três métodos: init, terminate e processResult. O último é chamado automaticamente e recursivamente passa por todas as estruturas e dados recebidos durante a análise.
Para gravar os dados convertidos no midi, usamos o Python MIDI toolkit (https://github.com/vishnubob/python-midi). Como estamos implementando a Prova de Conceito, não realizaremos a conversão de durações de notas e delta. Em vez disso, definimos valores fixos. Notas com duração de 0x7fff ou delta semelhante são simplesmente descartadas por enquanto.
Os recursos do editor de script interno são muito limitados, portanto todo o código deverá ser colocado em um arquivo.
gist.github.com/bkotov/71d7dfafebfe775616c4bd17d6ddfe7bEntão, vamos tentar converter o arquivo e ouvir o que temos
Hmm ... E ficou bem interessante. A primeira coisa que me ocorreu quando tentei formular como era a música sem estrutura. Vou tentar dar uma definição:
Música não estruturada - uma música com uma estrutura reduzida, construída em harmonia. As durações e os intervalos das notas são cancelados ou reduzidos para os mesmos valores.Uma espécie de barulho harmonioso. Seja perolado (por analogia com branco, azul, vermelho, rosa etc.), parece que ninguém adotou essa combinação.
Talvez devêssemos tentar treinar uma rede neural nos meus dados, talvez o resultado seja interessante.
A tarefa de aquecer a mente
Tudo isso é maravilhoso, mas o principal problema ainda não está resolvido. Precisamos converter as durações das notas em eventos NoteOff e o deslocamento de tempo do evento relativo ao início da medida em um delta de tempo entre eventos adjacentes. Vou tentar formular as condições do problema de maneira mais formal.
Desafio:
1
1
2
3
...
N
2
...
N
1
...
: 1
: 1920
: Int
: Int
: 9
: 0-127
: 0-127
: 0-1920 0xFF
: 0-1920 0xFF
, , 0xFF, =0xFF . , . = = 0xFF.
.
midi. :
:
: 9
: 0-127
: 0-127
: Int
:
: 8
: 0-127
: 0-127
: Int
A tarefa é um pouco simplificada. Em um arquivo SNG real, cada medida pode ter uma dimensão diferente. Além dos eventos Note On / Off, outros eventos também ocorrerão no fluxo, por exemplo, pressionando o pedal de sustentação ou alterando o tom usando pitchBend.
Vou dar a minha solução para esse problema no próximo artigo (se houver).
Resultados atuais
Como a solução com o script não se ajusta a um número arbitrário de arquivos, decidi escrever um conversor de console no Swift. Se eu escrevesse um conversor bidirecional, as estruturas gramaticais criadas seriam úteis para mim no código. Você pode exportá-los para estruturas C ou qualquer outro idioma usando a mesma funcionalidade de script criada no Synalize It! Um arquivo com um exemplo dessa exportação é criado automaticamente quando você seleciona um modelo de gramática.

No momento, o conversor está 99% completo (no formato que me convém em termos de funcionalidade). Eu pretendo colocar o código e a gramática no github.
Um exemplo, para o qual tudo foi iniciado,
você pode ouvir aqui .
Como esta peça soa pronta.