Cómo recuperé datos en un formato desconocido de una cinta magnética

Prehistoria

Siendo un amante del hardware retro, una vez compré un ZX Spectrum+ a un vendedor en el Reino Unido. Junto con la computadora, recibí varios casetes de audio con juegos (en el paquete original con instrucciones), así como programas grabados en casetes sin marcas especiales. Sorprendentemente, los datos de los casetes de hace 40 años se podían leer bien y pude descargar casi todos los juegos y programas de ellos.

Cómo recuperé datos en un formato desconocido de una cinta magnética

Sin embargo, en algunos casetes encontré grabaciones que claramente no fueron hechas por la computadora ZX Spectrum. Sonaban completamente diferentes y, a diferencia de las grabaciones del ordenador mencionado, no comenzaban con un breve gestor de arranque BÁSICO, que suele estar presente en las grabaciones de todos los programas y juegos.

Durante algún tiempo esto me persiguió: tenía muchas ganas de descubrir qué se escondía en ellos. Si pudiera leer la señal de audio como una secuencia de bytes, podría buscar caracteres o cualquier cosa que indique el origen de la señal. Una especie de retroarqueología.

Ahora que he recorrido todo el camino y miro las etiquetas de los casetes, sonrío porque

La respuesta estuvo justo frente a mis ojos todo el tiempo.
En la etiqueta del casete izquierdo está el nombre de la computadora TRS-80, y justo debajo el nombre del fabricante: “Fabricado por Radio Shack en EE. UU.”

(Si quieres mantener la intriga hasta el final, no te metas en spoiler)

Comparación de señales de audio.

En primer lugar, digitalicemos las grabaciones de audio. Puedes escuchar cómo suena:


Y como siempre suena la grabación del ordenador ZX Spectrum:


En ambos casos, al comienzo de la grabación hay un llamado tono piloto - un sonido de la misma frecuencia (en la primera grabación es muy corto <1 segundo, pero se distingue). El tono piloto indica a la computadora que se prepare para recibir datos. Por regla general, cada ordenador reconoce sólo su "propio" tono piloto por la forma de la señal y su frecuencia.

Es necesario decir algo sobre la forma de la señal en sí. Por ejemplo, en el ZX Spectrum su forma es rectangular:

Cómo recuperé datos en un formato desconocido de una cinta magnética

Cuando se detecta un tono piloto, el ZX Spectrum muestra barras rojas y azules alternadas en el borde de la pantalla para indicar que se ha reconocido la señal. Termina el tono piloto pulso sincronizado, que indica a la computadora que comience a recibir datos. Se caracteriza por una duración más corta (en comparación con el tono piloto y los datos posteriores) (ver figura)

Después de recibir el pulso de sincronización, la computadora registra cada subida/caída de la señal, midiendo su duración. Si la duración es inferior a un cierto límite, el bit 1 se escribe en la memoria; de lo contrario, 0. Los bits se recopilan en bytes y el proceso se repite hasta que se reciben N bytes. El número N normalmente se toma del encabezado del archivo descargado. La secuencia de carga es la siguiente:

  1. tono piloto
  2. encabezado (longitud fija), contiene el tamaño de los datos descargados (N), el nombre del archivo y el tipo
  3. tono piloto
  4. los datos en sí

Para asegurarse de que los datos se cargan correctamente, el ZX Spectrum lee el llamado byte de paridad (byte de paridad), que se calcula al guardar un archivo aplicando XOR en todos los bytes de los datos escritos. Al leer un archivo, la computadora calcula el byte de paridad a partir de los datos recibidos y, si el resultado difiere del guardado, muestra el mensaje de error "Error al cargar la cinta R". Estrictamente hablando, la computadora puede emitir este mensaje antes si, al leer, no puede reconocer un pulso (se perdió o su duración no corresponde a ciertos límites)

Entonces, veamos ahora cómo se ve una señal desconocida:

Cómo recuperé datos en un formato desconocido de una cinta magnética

Este es el tono piloto. La forma de la señal es significativamente diferente, pero está claro que la señal consiste en pulsos cortos repetidos de una determinada frecuencia. A una frecuencia de muestreo de 44100 Hz, la distancia entre los “picos” es de aproximadamente 48 muestras (lo que corresponde a una frecuencia de ~918 Hz). Recordemos esta cifra.

Veamos ahora el fragmento de datos:

Cómo recuperé datos en un formato desconocido de una cinta magnética

Si medimos la distancia entre pulsos individuales, resulta que la distancia entre pulsos "largos" sigue siendo de ~48 muestras, y entre los cortos, ~24. Mirando un poco hacia adelante, diré que al final resultó que los pulsos de "referencia" con una frecuencia de 918 Hz siguen continuamente, desde el principio hasta el final del archivo. Se puede suponer que al transmitir datos, si se encuentra un pulso adicional entre los pulsos de referencia, lo consideramos como el bit 1; en caso contrario, como el bit 0.

¿Qué pasa con el pulso de sincronización? Veamos el comienzo de los datos:

Cómo recuperé datos en un formato desconocido de una cinta magnética

El tono piloto termina y los datos comienzan inmediatamente. Un poco más tarde, después de analizar varias grabaciones de audio diferentes, pudimos descubrir que el primer byte de datos es siempre el mismo (10100101b, A5h). La computadora puede comenzar a leer datos después de recibirlos.

También puede prestar atención al desplazamiento del primer pulso de referencia inmediatamente después del último primero en el byte de sincronización. Se descubrió mucho más tarde en el proceso de desarrollo de un programa de reconocimiento de datos, cuando los datos al principio del archivo no se podían leer de manera estable.

Ahora intentemos describir un algoritmo que procesará un archivo de audio y cargará datos.

Cargando datos

Primero, veamos algunas suposiciones para mantener el algoritmo simple:

  1. Sólo consideraremos archivos en formato WAV;
  2. El archivo de audio debe comenzar con un tono piloto y no debe contener silencio al principio.
  3. El archivo fuente debe tener una frecuencia de muestreo de 44100 Hz. En este caso, la distancia entre los pulsos de referencia de 48 muestras ya está determinada y no necesitamos calcularla mediante programación;
  4. El formato de muestra puede ser cualquiera (8/16 bits/coma flotante) - ya que al leer podemos convertirlo al deseado;
  5. Suponemos que el archivo fuente está normalizado por amplitud, lo que debería estabilizar el resultado;

El algoritmo de lectura será el siguiente:

  1. Leemos el archivo en la memoria y al mismo tiempo convertimos el formato de muestra a 8 bits;
  2. Determine la posición del primer pulso en los datos de audio. Para hacer esto, es necesario calcular el número de muestras con la amplitud máxima. Para simplificar, lo calcularemos una vez manualmente. Guardémoslo en la variable prev_pos;
  3. Suma 48 a la posición del último pulso (pos:= prev_pos + 48)
  4. Dado que aumentar la posición en 48 no garantiza que llegaremos a la posición del siguiente pulso de referencia (defectos en la cinta, funcionamiento inestable del mecanismo de la unidad de cinta, etc.), debemos ajustar la posición del pulso pos. Para hacer esto, tome un pequeño dato (pos-8; pos+8) y encuentre el valor de amplitud máxima en él. La posición correspondiente al máximo se almacenará en la pos. Aquí 8 = 48/6 es una constante obtenida experimentalmente, lo que garantiza que determinaremos el máximo correcto y no afectará otros impulsos que puedan estar cerca. En casos muy malos, cuando la distancia entre pulsos es mucho menor o mayor que 48, se puede implementar una búsqueda forzada de pulso, pero dentro del alcance del artículo no describiré esto en el algoritmo;
  5. En el paso anterior, también sería necesario comprobar que se haya encontrado el pulso de referencia. Es decir, si simplemente se busca el máximo, esto no garantiza que el impulso esté presente en este segmento. En mi última implementación del programa de lectura, compruebo la diferencia entre los valores de amplitud máxima y mínima en un segmento, y si excede un cierto límite, cuento la presencia de un impulso. La pregunta también es qué hacer si no se encuentra el pulso de referencia. Hay 2 opciones: o los datos han terminado y sigue el silencio, o esto debe considerarse un error de lectura. Sin embargo, omitiremos esto para simplificar el algoritmo;
  6. En el siguiente paso, necesitamos determinar la presencia de un pulso de datos (bit 0 o 1), para esto tomamos la mitad del segmento (prev_pos;pos) middle_pos igual a middle_pos := (prev_pos+pos)/2 y en alguna vecindad de middle_pos en el segmento (middle_pos-8;middle_pos +8) calculemos la amplitud máxima y mínima. Si la diferencia entre ellos es mayor que 10, escribimos el bit 1 en el resultado; de lo contrario, 0. 10 es una constante obtenida experimentalmente;
  7. Guarde la posición actual en prev_pos (prev_pos := pos)
  8. Repetir desde el paso 3 hasta leer el archivo completo;
  9. La matriz de bits resultante debe guardarse como un conjunto de bytes. Como no tomamos en cuenta el byte de sincronización al leer, es posible que el número de bits no sea múltiplo de 8 y también se desconoce el desplazamiento de bits requerido. En la primera implementación del algoritmo, no sabía de la existencia del byte de sincronización y, por lo tanto, simplemente guardé 8 archivos con diferentes números de bits de desplazamiento. Uno de ellos contenía datos correctos. En el algoritmo final, simplemente elimino todos los bits hasta A5h, lo que me permite obtener inmediatamente el archivo de salida correcto.

Algoritmo en Ruby, para los interesados
Elegí Ruby como lenguaje para escribir el programa, porque... Lo programo la mayor parte del tiempo. La opción no es de alto rendimiento, pero la tarea de hacer que la velocidad de lectura sea lo más rápida posible no vale la 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

Después de haber probado varias variantes del algoritmo y las constantes, tuve la suerte de conseguir algo extremadamente interesante:

Cómo recuperé datos en un formato desconocido de una cinta magnética

Entonces, a juzgar por las cadenas de caracteres, tenemos un programa para trazar gráficos. Sin embargo, no hay palabras clave en el texto del programa. Todas las palabras clave están codificadas como bytes (cada valor > 80h). Ahora falta saber qué ordenador de los 80 podía guardar programas en este formato.

De hecho, es muy similar a un programa BÁSICO. La computadora ZX Spectrum almacena programas aproximadamente en el mismo formato en la memoria y los guarda en cinta. Por si acaso, verifiqué las palabras clave con mesa. Sin embargo, el resultado fue evidentemente negativo.

También verifiqué las palabras clave BÁSICAS de los populares Atari, Commodore 64 y varias otras computadoras de esa época, para las cuales pude encontrar documentación, pero sin éxito: mi conocimiento sobre los tipos de computadoras retro resultó no ser tan amplio.

Entonces decidí ir la lista, y luego mi mirada se posó en el nombre del fabricante Radio Shack y la computadora TRS-80. ¡Estos son los nombres que estaban escritos en las etiquetas de los casetes que estaban sobre mi mesa! No conocía estos nombres antes y no estaba familiarizado con la computadora TRS-80, por lo que me pareció que Radio Shack era un fabricante de casetes de audio como BASF, Sony o TDK, y el TRS-80 era el tiempo de reproducción. ¿Por qué no?

Computadora Tandy/Radio Shack TRS-80

Es muy probable que la grabación de audio en cuestión, que puse como ejemplo al principio del artículo, se haya realizado en un ordenador como este:

Cómo recuperé datos en un formato desconocido de una cinta magnética

Resultó que esta computadora y sus variedades (Modelo I/Modelo III/Modelo IV, etc.) fueron muy populares en algún momento (por supuesto, no en Rusia). Cabe destacar que el procesador que utilizaron también fue el Z80. Para esta computadora puedes encontrar en Internet. mucha información. En los años 80, la información informática se distribuía en revistas. Actualmente existen varios emuladores Computadoras para diferentes plataformas.

descargué el emulador trs80gp y por primera vez pude ver cómo funcionaba esta computadora. Por supuesto, la computadora no admitía salida de color, la resolución de la pantalla era solo de 128x48 píxeles, pero hubo muchas extensiones y modificaciones que podrían aumentar la resolución de la pantalla. También había muchas opciones para los sistemas operativos para esta computadora y opciones para implementar el lenguaje BASIC (que, a diferencia del ZX Spectrum, en algunos modelos ni siquiera estaba "flasheado" en la ROM y cualquier opción se podía cargar desde un disquete, al igual que el propio sistema operativo)

También encontré utilidad para convertir grabaciones de audio al formato CAS, que es compatible con emuladores, pero por alguna razón no fue posible leer grabaciones de mis casetes usándolos.

Habiendo descubierto el formato del archivo CAS (que resultó ser solo una copia bit a bit de los datos de la cinta que ya tenía a mano, excepto el encabezado con la presencia de un byte de sincronización), hice un Algunos cambios en mi programa y pude generar un archivo CAS funcional que funcionó en el emulador (TRS-80 Modelo III):

Cómo recuperé datos en un formato desconocido de una cinta magnética

Diseñé la última versión de la utilidad de conversión con determinación automática del primer pulso y la distancia entre pulsos de referencia como un paquete GEM, el código fuente está disponible en Github.

Conclusión

El camino que hemos recorrido resultó ser un viaje fascinante al pasado y me alegro de haber encontrado al final la respuesta. Entre otras cosas, yo:

  • Descubrí el formato para guardar datos en el ZX Spectrum y estudié las rutinas ROM integradas para guardar/leer datos de casetes de audio.
  • Me familiaricé con la computadora TRS-80 y sus variedades, estudié el sistema operativo, miré programas de muestra e incluso tuve la oportunidad de depurar códigos de máquina (después de todo, todos los mnemotécnicos del Z80 me son familiares)
  • Se escribió una utilidad completa para convertir grabaciones de audio al formato CAS, que puede leer datos que la utilidad "oficial" no reconoce.

Fuente: habr.com

Añadir un comentario