Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Préhistoire

Étant un amateur de matériel rétro, j'ai acheté un jour un ZX Spectrum+ auprès d'un vendeur au Royaume-Uni. Inclus avec l'ordinateur lui-même, j'ai reçu plusieurs cassettes audio avec des jeux (dans l'emballage d'origine avec notice), ainsi que des programmes enregistrés sur des cassettes sans marquage particulier. Étonnamment, les données des cassettes vieilles de 40 ans étaient bien lisibles et j'ai pu télécharger presque tous les jeux et programmes à partir d'elles.

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Cependant, sur certaines cassettes, j'ai trouvé des enregistrements qui n'étaient clairement pas réalisés par l'ordinateur ZX Spectrum. Ils sonnaient complètement différemment et, contrairement aux enregistrements de l'ordinateur mentionné, ils ne démarraient pas avec un court chargeur de démarrage BASIC, qui est généralement présent dans les enregistrements de tous les programmes et jeux.

Pendant un certain temps, cela m'a hanté - je voulais vraiment découvrir ce qui s'y cachait. Si vous pouviez lire le signal audio sous forme de séquence d’octets, vous pourriez rechercher des caractères ou tout ce qui indique l’origine du signal. Une sorte de rétro-archéologie.

Maintenant que j'ai parcouru le chemin et regardé les étiquettes des cassettes elles-mêmes, je souris parce que

la réponse était juste devant mes yeux depuis le début
Sur l'étiquette de la cassette de gauche figure le nom de l'ordinateur TRS-80, et juste en dessous le nom du fabricant : « Manufactured by Radio Shack in USA »

(Si vous souhaitez garder l'intrigue jusqu'au bout, ne passez pas sous le spoiler)

Comparaison des signaux audio

Tout d’abord, numérisons les enregistrements audio. Vous pouvez écouter à quoi cela ressemble :


Et comme d'habitude l'enregistrement de l'ordinateur ZX Spectrum sonne :


Dans les deux cas, au début de l'enregistrement, il y a ce qu'on appelle tonalité pilote - un son de même fréquence (dans le premier enregistrement il est très court <1 seconde, mais se distingue). La tonalité pilote signale à l'ordinateur de se préparer à recevoir des données. En règle générale, chaque ordinateur ne reconnaît que sa « propre » tonalité pilote par la forme du signal et sa fréquence.

Il est nécessaire de dire quelque chose sur la forme du signal lui-même. Par exemple, sur le ZX Spectrum sa forme est rectangulaire :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Lorsqu'une tonalité pilote est détectée, le ZX Spectrum affiche en alternance des barres rouges et bleues sur le bord de l'écran pour indiquer que le signal a été reconnu. La tonalité pilote se termine impulsion de synchronisation, qui signale à l'ordinateur de commencer à recevoir des données. Elle se caractérise par une durée plus courte (par rapport à la tonalité pilote et aux données ultérieures) (voir figure)

Une fois l'impulsion de synchronisation reçue, l'ordinateur enregistre chaque montée/descente du signal, mesurant sa durée. Si la durée est inférieure à une certaine limite, le bit 1 est écrit en mémoire, sinon 0. Les bits sont rassemblés en octets et le processus est répété jusqu'à ce que N octets soient reçus. Le numéro N provient généralement de l’en-tête du fichier téléchargé. La séquence de chargement est la suivante :

  1. tonalité pilote
  2. en-tête (longueur fixe), contient la taille des données téléchargées (N), le nom et le type du fichier
  3. tonalité pilote
  4. les données elles-mêmes

Pour s'assurer que les données sont chargées correctement, le ZX Spectrum lit ce qu'on appelle octet de parité (octet de parité), qui est calculé lors de la sauvegarde d'un fichier en effectuant un XOR sur tous les octets des données écrites. Lors de la lecture d'un fichier, l'ordinateur calcule l'octet de parité à partir des données reçues et, si le résultat diffère de celui enregistré, affiche le message d'erreur « Erreur de chargement de la bande R ». À proprement parler, l'ordinateur peut émettre ce message plus tôt si, lors de la lecture, il ne parvient pas à reconnaître une impulsion (manquée ou sa durée ne correspond pas à certaines limites)

Voyons maintenant à quoi ressemble un signal inconnu :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

C'est le ton pilote. La forme du signal est sensiblement différente, mais il est clair que le signal consiste en la répétition d'impulsions courtes d'une certaine fréquence. A une fréquence d'échantillonnage de 44100 Hz, la distance entre les « pics » est d'environ 48 échantillons (ce qui correspond à une fréquence de ~918 Hz). Rappelons ce chiffre.

Examinons maintenant le fragment de données :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Si nous mesurons la distance entre les impulsions individuelles, il s'avère que la distance entre les impulsions « longues » est toujours d'environ 48 échantillons et entre les courtes - d'environ 24. En regardant un peu en avant, je dirai qu'au final il s'est avéré que des impulsions « de référence » avec une fréquence de 918 Hz se succèdent en continu, du début à la fin du fichier. On peut supposer que lors de la transmission de données, si une impulsion supplémentaire est rencontrée entre les impulsions de référence, nous la considérons comme le bit 1, sinon 0.

Qu'en est-il de l'impulsion de synchronisation ? Regardons le début des données :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

La tonalité pilote se termine et les données commencent immédiatement. Un peu plus tard, après avoir analysé plusieurs enregistrements audio différents, nous avons pu découvrir que le premier octet de données est toujours le même (10100101b, A5h). L'ordinateur peut commencer à lire les données après les avoir reçues.

Vous pouvez également faire attention au décalage de la première impulsion de référence immédiatement après la dernière 1ère de l'octet de synchronisation. Cela a été découvert bien plus tard, lors du développement d'un programme de reconnaissance de données, lorsque les données situées au début du fichier ne pouvaient pas être lues de manière stable.

Essayons maintenant de décrire un algorithme qui traitera un fichier audio et chargera des données.

Chargement des données

Tout d’abord, examinons quelques hypothèses pour que l’algorithme reste simple :

  1. Nous ne considérerons que les fichiers au format WAV ;
  2. Le fichier audio doit commencer par une tonalité pilote et ne doit pas contenir de silence au début
  3. Le fichier source doit avoir une fréquence d'échantillonnage de 44100 48 Hz. Dans ce cas, la distance entre les impulsions de référence de XNUMX échantillons est déjà déterminée et nous n’avons pas besoin de la calculer par programme ;
  4. Le format de l'échantillon peut être n'importe lequel (8/16 bits/virgule flottante) - puisque lors de la lecture, nous pouvons le convertir en celui souhaité ;
  5. Nous supposons que le fichier source est normalisé en amplitude, ce qui devrait stabiliser le résultat ;

L'algorithme de lecture sera le suivant :

  1. Nous lisons le fichier en mémoire, tout en convertissant le format de l'échantillon en 8 bits ;
  2. Déterminez la position de la première impulsion dans les données audio. Pour ce faire, vous devez calculer le nombre d'échantillons avec l'amplitude maximale. Pour plus de simplicité, nous le calculerons une fois manuellement. Sauvons-le dans la variable prev_pos ;
  3. Ajoutez 48 à la position de la dernière impulsion (pos := prev_pos + 48)
  4. Étant donné qu'augmenter la position de 48 ne garantit pas que nous arriverons à la position de la prochaine impulsion de référence (défauts de la bande, fonctionnement instable du mécanisme du lecteur de bande, etc.), nous devons ajuster la position de l'impulsion pos. Pour ce faire, prenez une petite donnée (pos-8;pos+8) et trouvez-y la valeur d'amplitude maximale. La position correspondant au maximum sera mémorisée en pos. Ici, 8 = 48/6 est une constante obtenue expérimentalement, ce qui garantit que nous déterminerons le maximum correct et n'affecterons pas les autres impulsions pouvant être à proximité. Dans les très mauvais cas, lorsque la distance entre les impulsions est bien inférieure ou supérieure à 48, vous pouvez mettre en œuvre une recherche forcée d'une impulsion, mais dans le cadre de l'article je ne décrirai pas cela dans l'algorithme ;
  5. À l’étape précédente, il serait également nécessaire de vérifier que l’impulsion de référence a été trouvée. Autrement dit, si vous recherchez simplement le maximum, cela ne garantit pas que l'impulsion soit présente dans ce segment. Dans ma dernière implémentation du programme de lecture, je vérifie la différence entre les valeurs d'amplitude maximale et minimale sur un segment, et si elle dépasse une certaine limite, je compte la présence d'une impulsion. La question est également de savoir que faire si l’impulsion de référence n’est pas trouvée. Il existe 2 options : soit les données sont terminées et un silence s'ensuit, soit cela doit être considéré comme une erreur de lecture. Cependant, nous omettrons cela pour simplifier l’algorithme ;
  6. A l'étape suivante, nous devons déterminer la présence d'une impulsion de données (bit 0 ou 1), pour cela nous prenons le milieu du segment (prev_pos;pos) middle_pos égal à middle_pos := (prev_pos+pos)/2 et dans un quartier de middle_pos sur le segment (middle_pos-8;middle_pos +8), calculons l'amplitude maximale et minimale. Si la différence entre eux est supérieure à 10, on écrit le bit 1 dans le résultat, sinon 0, 10 est une constante obtenue expérimentalement ;
  7. Enregistrez la position actuelle dans prev_pos (prev_pos := pos)
  8. Répétez à partir de l'étape 3 jusqu'à ce que nous lisions l'intégralité du fichier ;
  9. Le tableau de bits résultant doit être enregistré sous forme d’ensemble d’octets. Comme nous n'avons pas pris en compte l'octet de synchronisation lors de la lecture, le nombre de bits peut ne pas être un multiple de 8 et le décalage binaire requis est également inconnu. Dans la première implémentation de l'algorithme, je ne connaissais pas l'existence de l'octet de synchronisation et j'ai donc simplement enregistré 8 fichiers avec différents nombres de bits de décalage. L'un d'eux contenait des données correctes. Dans l'algorithme final, je supprime simplement tous les bits jusqu'à A5h, ce qui me permet d'obtenir immédiatement le bon fichier de sortie.

Algorithme en Ruby, pour ceux que ça intéresse
J'ai choisi Ruby comme langage pour écrire le programme, parce que... Je programme dessus la plupart du temps. L'option n'est pas performante, mais la tâche consistant à rendre la vitesse de lecture aussi rapide que possible n'en vaut pas la peine.

# Используем 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*")

Résultat

Après avoir essayé plusieurs variantes de l'algorithme et des constantes, j'ai eu la chance d'obtenir quelque chose d'extrêmement intéressant :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Ainsi, à en juger par les chaînes de caractères, nous avons un programme pour tracer des graphiques. Cependant, il n'y a aucun mot-clé dans le texte du programme. Tous les mots-clés sont codés sous forme d'octets (chaque valeur > 80h). Il faut maintenant savoir quel ordinateur des années 80 pouvait sauvegarder des programmes dans ce format.

En fait, il ressemble beaucoup à un programme BASIC. L'ordinateur ZX Spectrum stocke les programmes à peu près dans le même format en mémoire et les enregistre sur bande. Juste au cas où, j'ai vérifié les mots-clés par rapport à table. Mais le résultat fut évidemment négatif.

J'ai également vérifié les mots-clés BASIC du populaire Atari, du Commodore 64 et de plusieurs autres ordinateurs de l'époque, pour lesquels j'ai pu trouver de la documentation, mais sans succès - ma connaissance des types d'ordinateurs rétro s'est avérée moins large.

Puis j'ai décidé d'y aller la liste, puis mon regard s'est posé sur le nom du fabricant Radio Shack et de l'ordinateur TRS-80. Ce sont les noms qui étaient inscrits sur les étiquettes des cassettes qui traînaient sur ma table ! Je ne connaissais pas ces noms auparavant et je n'étais pas familier avec l'ordinateur TRS-80, il m'a donc semblé que Radio Shack était un fabricant de cassettes audio comme BASF, Sony ou TDK, et que le TRS-80 était le temps de lecture. Pourquoi pas?

Ordinateur Tandy/Radio Shack TRS-80

Il est fort probable que l'enregistrement audio en question, que j'ai donné en exemple en début d'article, ait été réalisé sur un ordinateur comme celui-ci :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

Il s'est avéré que cet ordinateur et ses variantes (Modèle I/Modèle III/Modèle IV, etc.) étaient très populaires à une époque (bien sûr, pas en Russie). Il est à noter que le processeur utilisé était également le Z80. Pour cet ordinateur, vous pouvez trouver sur Internet beaucoup d'informations. Dans les années 80, l'information informatique était diffusée dans les magazines. Il existe actuellement plusieurs émulateurs ordinateurs pour différentes plates-formes.

J'ai téléchargé l'émulateur trs80gp et pour la première fois j'ai pu voir comment fonctionnait cet ordinateur. Bien sûr, l'ordinateur ne prenait pas en charge la sortie couleur ; la résolution de l'écran n'était que de 128 x 48 pixels, mais de nombreuses extensions et modifications pouvaient augmenter la résolution de l'écran. Il y avait également de nombreuses options pour les systèmes d'exploitation pour cet ordinateur et des options pour implémenter le langage BASIC (qui, contrairement au ZX Spectrum, dans certains modèles n'était même pas « flashé » dans la ROM et n'importe quelle option pouvait être chargée à partir d'une disquette, tout comme le système d'exploitation lui-même)

j'ai aussi trouvé utilitaire pour convertir les enregistrements audio au format CAS, qui est pris en charge par les émulateurs, mais pour une raison quelconque, il n'a pas été possible de lire les enregistrements de mes cassettes en les utilisant.

Après avoir compris le format de fichier CAS (qui s'est avéré n'être qu'une copie bit par bit des données de la bande que j'avais déjà sous la main, à l'exception de l'en-tête avec la présence d'un octet de synchronisation), j'ai fait un quelques modifications apportées à mon programme et j'ai pu générer un fichier CAS fonctionnel qui fonctionnait dans l'émulateur (TRS-80 modèle III) :

Comment j'ai récupéré des données dans un format inconnu à partir d'une bande magnétique

J'ai conçu la dernière version de l'utilitaire de conversion avec détermination automatique de la première impulsion et de la distance entre les impulsions de référence sous forme de package GEM, le code source est disponible sur Github.

Conclusion

Le chemin que nous avons parcouru s’est avéré être un voyage fascinant dans le passé et je suis heureux d’avoir finalement trouvé la réponse. Entre autres, je :

  • J'ai compris le format de sauvegarde des données dans le ZX Spectrum et étudié les routines ROM intégrées pour sauvegarder/lire les données des cassettes audio.
  • Je me suis familiarisé avec l'ordinateur TRS-80 et ses variétés, étudié le système d'exploitation, examiné des exemples de programmes et j'ai même eu l'occasion de déboguer des codes machine (après tout, tous les mnémoniques du Z80 me sont familiers)
  • A écrit un utilitaire à part entière pour convertir des enregistrements audio au format CAS, capable de lire des données non reconnues par l'utilitaire « officiel »

Source: habr.com

Ajouter un commentaire