Como recuperei dados em formato desconhecido de fita magnética

Pré-história

Sendo um amante de hardware retrô, certa vez comprei um ZX Spectrum+ de um vendedor no Reino Unido. Acompanhando o próprio computador, recebi diversas fitas de áudio com jogos (na embalagem original com instruções), além de programas gravados em fitas sem marcações especiais. Surpreendentemente, os dados das fitas cassete de 40 anos eram bem legíveis e consegui baixar quase todos os jogos e programas delas.

Como recuperei dados em formato desconhecido de fita magnética

No entanto, em algumas fitas encontrei gravações que claramente não foram feitas pelo computador ZX Spectrum. Soavam completamente diferentes e, ao contrário das gravações do referido computador, não iniciavam com um carregador BASIC curto, que costuma estar presente nas gravações de todos os programas e jogos.

Por algum tempo isso me assombrou - eu realmente queria descobrir o que estava escondido neles. Se você pudesse ler o sinal de áudio como uma sequência de bytes, poderia procurar caracteres ou qualquer coisa que indicasse a origem do sinal. Uma espécie de retro-arqueologia.

Agora que percorri todo o caminho e olhei as etiquetas das fitas, sorrio porque

a resposta estava bem na frente dos meus olhos o tempo todo
Na etiqueta da fita esquerda está o nome do computador TRS-80, e logo abaixo o nome do fabricante: “Fabricado pela Radio Shack nos EUA”

(Se quiser manter a intriga até o fim, não vá abaixo do spoiler)

Comparação de sinais de áudio

Em primeiro lugar, vamos digitalizar as gravações de áudio. Você pode ouvir o que parece:


E como de costume, a gravação do computador ZX Spectrum soa:


Em ambos os casos, no início da gravação existe um chamado tom piloto - um som da mesma frequência (na primeira gravação é muito curto <1 segundo, mas é distinguível). O tom piloto sinaliza ao computador para se preparar para receber dados. Via de regra, cada computador reconhece apenas seu “próprio” tom piloto pelo formato do sinal e sua frequência.

É necessário dizer algo sobre o próprio formato do sinal. Por exemplo, no ZX Spectrum seu formato é retangular:

Como recuperei dados em formato desconhecido de fita magnética

Quando um tom piloto é detectado, o ZX Spectrum exibe barras vermelhas e azuis alternadas na borda da tela para indicar que o sinal foi reconhecido. O tom piloto termina pulso sincronizado, que sinaliza ao computador para começar a receber dados. É caracterizado por uma duração mais curta (em comparação com o tom piloto e dados subsequentes) (ver figura)

Depois que o pulso de sincronização é recebido, o computador registra cada aumento/queda do sinal, medindo sua duração. Se a duração for menor que um determinado limite, o bit 1 é gravado na memória, caso contrário, 0. Os bits são coletados em bytes e o processo é repetido até que N bytes sejam recebidos. O número N geralmente é retirado do cabeçalho do arquivo baixado. A sequência de carregamento é a seguinte:

  1. tom piloto
  2. cabeçalho (comprimento fixo), contém o tamanho dos dados baixados (N), nome e tipo do arquivo
  3. tom piloto
  4. os próprios dados

Para garantir que os dados sejam carregados corretamente, o ZX Spectrum lê o chamado byte de paridade (byte de paridade), que é calculado ao salvar um arquivo por meio de XOR em todos os bytes dos dados gravados. Ao ler um arquivo, o computador calcula o byte de paridade a partir dos dados recebidos e, caso o resultado seja diferente do salvo, exibe a mensagem de erro “Erro de carregamento da fita R”. A rigor, o computador pode emitir esta mensagem mais cedo se, durante a leitura, não conseguir reconhecer um pulso (perdido ou sua duração não corresponde a determinados limites)

Então, vamos agora ver como é um sinal desconhecido:

Como recuperei dados em formato desconhecido de fita magnética

Este é o tom piloto. A forma do sinal é significativamente diferente, mas é claro que o sinal consiste na repetição de pulsos curtos de uma determinada frequência. A uma frequência de amostragem de 44100 Hz, a distância entre os “picos” é de aproximadamente 48 amostras (o que corresponde a uma frequência de ~918 Hz). Vamos lembrar este número.

Vejamos agora o fragmento de dados:

Como recuperei dados em formato desconhecido de fita magnética

Se medirmos a distância entre pulsos individuais, verifica-se que a distância entre pulsos “longos” ainda é de ~48 amostras, e entre os curtos - ~24. Olhando um pouco à frente, direi que no final descobri que pulsos de “referência” com frequência de 918 Hz seguem continuamente, do início ao fim do arquivo. Pode-se supor que, ao transmitir dados, se um pulso adicional for encontrado entre os pulsos de referência, consideramos isso como bit 1, caso contrário, 0.

E o pulso de sincronização? Vejamos o início dos dados:

Como recuperei dados em formato desconhecido de fita magnética

O tom piloto termina e os dados começam imediatamente. Um pouco mais tarde, após analisar várias gravações de áudio diferentes, descobrimos que o primeiro byte de dados é sempre o mesmo (10100101b, A5h). O computador pode começar a ler os dados após recebê-los.

Você também pode prestar atenção ao deslocamento do primeiro pulso de referência imediatamente após o último primeiro no byte de sincronização. Foi descoberto muito mais tarde, no processo de desenvolvimento de um programa de reconhecimento de dados, quando os dados no início do arquivo não podiam ser lidos de forma estável.

Agora vamos tentar descrever um algoritmo que irá processar um arquivo de áudio e carregar dados.

Carregando dados

Primeiro, vejamos algumas suposições para manter o algoritmo simples:

  1. Consideraremos apenas arquivos no formato WAV;
  2. O arquivo de áudio deve começar com um tom piloto e não deve conter silêncio no início
  3. O arquivo de origem deve ter uma taxa de amostragem de 44100 Hz. Neste caso, a distância entre os pulsos de referência de 48 amostras já está determinada e não precisamos calculá-la programaticamente;
  4. O formato da amostra pode ser qualquer (8/16 bits/ponto flutuante) - pois na leitura podemos convertê-lo para o desejado;
  5. Assumimos que o arquivo de origem é normalizado pela amplitude, o que deve estabilizar o resultado;

O algoritmo de leitura será o seguinte:

  1. Lemos o arquivo na memória, ao mesmo tempo que convertemos o formato de amostra para 8 bits;
  2. Determine a posição do primeiro pulso nos dados de áudio. Para fazer isso, você precisa calcular o número da amostra com amplitude máxima. Para simplificar, calcularemos uma vez manualmente. Vamos salvá-lo na variável prev_pos;
  3. Adicione 48 à posição do último pulso (pos := prev_pos + 48)
  4. Como aumentar a posição em 48 não garante que chegaremos à posição do próximo pulso de referência (defeitos na fita, operação instável do mecanismo de acionamento da fita, etc.), precisamos ajustar a posição do pulso pos. Para fazer isso, pegue um pequeno dado (pos-8;pos+8) e encontre nele o valor máximo de amplitude. A posição correspondente ao máximo será armazenada na pos. Aqui 8 = 48/6 é uma constante obtida experimentalmente, o que garante que determinaremos o máximo correto e não afetaremos outros impulsos que possam estar próximos. Em casos muito ruins, quando a distância entre os pulsos é muito menor ou maior que 48, você pode implementar uma busca forçada por um pulso, mas dentro do escopo do artigo não descreverei isso no algoritmo;
  5. Na etapa anterior, também seria necessário verificar se o pulso de referência foi encontrado. Ou seja, se você simplesmente busca o máximo, isso não garante que o impulso esteja presente neste segmento. Na minha última implementação do programa de leitura, verifico a diferença entre os valores máximo e mínimo de amplitude de um segmento e, caso ultrapasse um determinado limite, conto a presença de um impulso. A questão também é o que fazer se o pulso de referência não for encontrado. Existem 2 opções: ou os dados terminaram e segue-se o silêncio, ou isso deve ser considerado um erro de leitura. Contudo, omitiremos isso para simplificar o algoritmo;
  6. Na próxima etapa, precisamos determinar a presença de um pulso de dados (bit 0 ou 1), para isso tomamos o meio do segmento (prev_pos;pos) middle_pos igual a middle_pos := (prev_pos+pos)/2 e em alguma vizinhança de middle_pos no segmento (middle_pos-8;middle_pos +8) vamos calcular a amplitude máxima e mínima. Se a diferença entre eles for superior a 10, escrevemos o bit 1 no resultado, caso contrário 0. 10 é uma constante obtida experimentalmente;
  7. Salve a posição atual em prev_pos (prev_pos := pos)
  8. Repita a partir do passo 3 até ler o arquivo inteiro;
  9. A matriz de bits resultante deve ser salva como um conjunto de bytes. Como não levamos em consideração o byte de sincronização durante a leitura, o número de bits pode não ser múltiplo de 8 e o deslocamento de bits necessário também é desconhecido. Na primeira implementação do algoritmo, eu não sabia da existência do byte de sincronização e, portanto, simplesmente salvei 8 arquivos com diferentes números de bits de deslocamento. Um deles continha dados corretos. No algoritmo final, simplesmente removo todos os bits até A5h, o que me permite obter imediatamente o arquivo de saída correto

Algoritmo em Ruby, para os interessados
Escolhi Ruby como linguagem para escrever o programa, porque... Eu programo nele na maioria das vezes. A opção não é de alto desempenho, mas a tarefa de tornar a velocidade de leitura a mais rápida possível não vale a pena.

# Используем gem 'wavefile'
require 'wavefile'

reader = WaveFile::Reader.new('input.wav')
samples = []
format = WaveFile::Format.new(:mono, :pcm_8, 44100)

# Читаем WAV файл, конвертируем в формат Mono, 8 bit 
# Массив samples будет состоять из байт со значениями 0-255
reader.each_buffer(10000) do |buffer|
  samples += buffer.convert(format).samples
end

# Позиция первого импульса (вместо 0)
prev_pos = 0
# Расстояние между импульсами
distance = 48
# Значение расстояния для окрестности поиска локального максимума
delta = (distance / 6).floor
# Биты будем сохранять в виде строки из "0" и "1"
bits = ""

loop do
  # Рассчитываем позицию следующего импульса
  pos = prev_pos + distance
  
  # Выходим из цикла если данные закончились 
  break if pos + delta >= samples.size

  # Корректируем позицию pos обнаружением максимума на отрезке [pos - delta;pos + delta]
  (pos - delta..pos + delta).each { |p| pos = p if samples[p] > samples[pos] }

  # Находим середину отрезка [prev_pos;pos]
  middle_pos = ((prev_pos + pos) / 2).floor

  # Берем окрестность в середине 
  sample = samples[middle_pos - delta..middle_pos + delta]

  # Определяем бит как "1" если разница между максимальным и минимальным значением на отрезке превышает 10
  bit = sample.max - sample.min > 10
  bits += bit ? "1" : "0"
end

# Определяем синхро-байт и заменяем все предшествующие биты на 256 бит нулей (согласно спецификации формата) 
bits.gsub! /^[01]*?10100101/, ("0" * 256) + "10100101"

# Сохраняем выходной файл, упаковывая биты в байты
File.write "output.cas", [bits].pack("B*")

resultado

Depois de tentar diversas variantes do algoritmo e constantes, tive a sorte de conseguir algo extremamente interessante:

Como recuperei dados em formato desconhecido de fita magnética

Portanto, a julgar pelas cadeias de caracteres, temos um programa para traçar gráficos. No entanto, não há palavras-chave no texto do programa. Todas as palavras-chave são codificadas como bytes (cada valor > 80h). Agora precisamos descobrir qual computador da década de 80 poderia salvar programas neste formato.

Na verdade, é muito semelhante a um programa BASIC. O computador ZX Spectrum armazena programas aproximadamente no mesmo formato na memória e salva os programas em fita. Por precaução, verifiquei as palavras-chave uma mesa. No entanto, o resultado foi obviamente negativo.

Também verifiquei as palavras-chave BASIC do popular Atari, Commodore 64 e vários outros computadores da época, para os quais consegui encontrar documentação, mas sem sucesso - meu conhecimento dos tipos de computadores retrô acabou não sendo tão amplo.

Então eu decidi ir a lista, e então meu olhar caiu sobre o nome do fabricante Radio Shack e do computador TRS-80. Esses são os nomes que estavam escritos nas etiquetas das fitas que estavam na minha mesa! Eu não conhecia esses nomes antes e não estava familiarizado com o computador TRS-80, então me pareceu que a Radio Shack era um fabricante de cassetes de áudio como BASF, Sony ou TDK, e o TRS-80 era o tempo de reprodução. Por que não?

Computador Tandy/Radio Shack TRS-80

É muito provável que a gravação de áudio em questão, que dei como exemplo no início do artigo, tenha sido feita em um computador como este:

Como recuperei dados em formato desconhecido de fita magnética

Descobriu-se que este computador e suas variedades (Modelo I/Modelo III/Modelo IV, etc.) eram muito populares em uma época (é claro, não na Rússia). Vale ressaltar que o processador utilizado por eles também foi o Z80. Para este computador você pode encontrar na Internet muita informação. Na década de 80, a informação informática era distribuída em revistas. Neste momento existem vários emuladores computadores para diferentes plataformas.

baixei o emulador trs80gp e pela primeira vez pude ver como esse computador funcionava. É claro que o computador não suportava saída colorida; a resolução da tela era de apenas 128x48 pixels, mas havia muitas extensões e modificações que poderiam aumentar a resolução da tela. Havia também muitas opções de sistemas operacionais para este computador e opções de implementação da linguagem BASIC (que, ao contrário do ZX Spectrum, em alguns modelos nem era “flashed” na ROM e qualquer opção podia ser carregada de um disquete, assim como o próprio sistema operacional)

Eu também encontrei utilidade para converter gravações de áudio para o formato CAS, que é suportado por emuladores, mas por algum motivo não foi possível ler as gravações das minhas fitas usando-as.

Tendo descoberto o formato do arquivo CAS (que acabou sendo apenas uma cópia bit a bit dos dados da fita que eu já tinha em mãos, exceto o cabeçalho com a presença de um byte de sincronização), fiz um poucas alterações em meu programa e consegui gerar um arquivo CAS funcional que funcionou no emulador (TRS-80 Modelo III):

Como recuperei dados em formato desconhecido de fita magnética

Projetei a versão mais recente do utilitário de conversão com determinação automática do primeiro pulso e da distância entre os pulsos de referência como um pacote GEM, o código-fonte está disponível em Github.

Conclusão

O caminho que percorremos revelou-se uma viagem fascinante ao passado e estou feliz por ter encontrado a resposta. Entre outras coisas, eu:

  • Eu descobri o formato para salvar dados no ZX Spectrum e estudei as rotinas ROM integradas para salvar/ler dados de fitas de áudio
  • Conheci o computador TRS-80 e suas variedades, estudei o sistema operacional, observei programas de amostra e ainda tive a oportunidade de fazer depuração em códigos de máquina (afinal, todos os mnemônicos do Z80 me são familiares)
  • Escreveu um utilitário completo para converter gravações de áudio para o formato CAS, que pode ler dados que não são reconhecidos pelo utilitário “oficial”

Fonte: habr.com

Adicionar um comentário