[DCMOTO] Emulation du générateur de son SN76489

Couvre tous les domaines de l'émulation ou de la virtualisation ainsi que les discussions sur les divers outils associés.

Modérateurs : Papy.G, fneck, Carl

Avatar de l’utilisateur
fxrobin
Messages : 102
Inscription : 07 mars 2019 13:51
Localisation : RENNES
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par fxrobin »

__sam__ a écrit : 30 janv. 2023 10:57 Tout ceci pourrait suggérer une histoire d'arrondis ou de PGCD entre la fréquence d'échantillonnage et le quanta de cycles des instructions de la boucle (25kHz=40cycles par échantillons et 40 se divise mieux en 5 cycles qu'en 15 cycles).
Je me permets de rajouter un petit détail qui nous a mis sur la voie, en regardant le code source de Theodore (emulateur Core RetroArch), c'est que ce dernier se fonde exclusivement sur un taux d'échantillage à 22 Khz (et non pas 25 Khz comme DCMoto) et que, de fait, il y a environ 45 cycles par échantillon, qui se divise parfaitement pour 15 cycles, en 3.

Code : Tout sélectionner

#define AUDIO_SAMPLE_RATE 22050

void retro_run(void)
{
  bool updated;
  int i;
  int mcycles; // nb of thousandths of cycles between 2 samples
  int icycles; // integer number of cycles between 2 samples
  int16_t audio_sample;
  // 45 cycles of the 6809 at 992250 Hz = one sample at 22050 Hz
  for(i = 0; i < AUDIO_SAMPLE_PER_FRAME; i++)
  {
    // Computes the nb of cycles between 2 samples and runs the emulation for this nb of cycles
    // Nb of theoretical cycles for this period of time =
    // theoretical number + previous remaining - cycles in excess during the previous period
    mcycles = 1000 * CPU_FREQUENCY / AUDIO_SAMPLE_RATE; // theoretical thousandths of cycles
    mcycles += excess;                       // corrected thousandths of cycles
    icycles = mcycles / 1000;                // integer number of cycles to run
    excess = mcycles - 1000 * icycles;       // remaining to do the next time
    excess -= 1000 * Run(icycles);           // remove thousandths in excess
    audio_sample = GetAudioSample();
    audio_stereo_buffer[(i << 1) + 0] = audio_stereo_buffer[(i << 1) + 1] = audio_sample;
  }

...
...



Bon courage Daniel pour la résolution du petit problème.
Fan d'ATARI 2600, de THOMSON MO5-TO8 et d'ATARI ST
Mes articles : https://www.fxjavadevblog.fr/retro-programming/
Membre du groupe wide-dot.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par __sam__ »

...nous a mis sur la voie
Ca aurait pu être la voix avec le SN76489 :lol:

Bon après les 15 cycles sont à titre informatif. Le moteur de Bentoc utilise 14 cycles, mais le principe est le même: il faut un gros bloc d'instructions "lourdes" pour voir un effet. L'effet disparait assez vite s'il y a aussi de nombreuses petites opérations à coté car je suppose que ca doit régulariser l'écart entre les horloges interne et externe qui animent l'émulation, écart qui doit d'une certaine façon s'accumuler avec les "grosses" instructions.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

Avec ces explications limpides la correction du bug a été un jeu d'enfant. Merci encore à vous trois.
La version 2023.01.30 de dcmoto corrige le problème : http://dcmoto.free.fr/emulateur/index.html
(Il faut peut-être rafraîchir la page dans votre navigateur pour afficher la dernière version)

Explications :

Dans dcmoto le processeur est synchronisé sur la période d'échantillonnage de la sortie audio.
Par exemple, pour une fréquence de sortie de 25 kHz, il faut exécuter 40 cycles du processeur à 1 MHz (1000000 / 25000 = 40).

Le comptage des cycles intervient à la fin de chaque instruction. La chance de tomber exactement entre deux instructions à la fin de la période d'échantillonnage est faible, surtout avec des instructions longues. Des cycles excédentaires sont donc exécutés.

Ces cycles excédentaires sont retranchés au nombre de cycles théoriques pour la prochaine période d'échantillonnage.

Le mécanisme fonctionnait parfaitement bien dans dcmoto 2017.07.14
Le 19/08/2017 j'ai fait une simplification du calcul pour économiser une variable et je me suis planté.
Il y a plus de cinq ans. Disons que c'est une erreur de jeunesse :mrgreen:

Je suis à la fois content d'avoir trouvé grâce à vous et désolé pour le dérangement et la perte de temps :oops:
Daniel
L'obstacle augmente mon ardeur.
Avatar de l’utilisateur
fxrobin
Messages : 102
Inscription : 07 mars 2019 13:51
Localisation : RENNES
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par fxrobin »

Super, bravo Daniel !
Fan d'ATARI 2600, de THOMSON MO5-TO8 et d'ATARI ST
Mes articles : https://www.fxjavadevblog.fr/retro-programming/
Membre du groupe wide-dot.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par __sam__ »

En même temps enquêter sur ce genre de bug vaut bien le film-policier du Dimanche-soir...
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Bentoc
Messages : 178
Inscription : 14 sept. 2019 13:35
Localisation : Var - France

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Bentoc »

La synchro est maintenant parfaite ! excellent Daniel.
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

Theodore est dérivé d'une version ancienne de mon émulateur dcto8d (2009). Elle est antérieure à l'introduction du bug et a donc le bon timing.
Daniel
L'obstacle augmente mon ardeur.
Avatar de l’utilisateur
fxrobin
Messages : 102
Inscription : 07 mars 2019 13:51
Localisation : RENNES
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par fxrobin »

Oui nous savions que c'était un dérivé (ton nom est partout dans le code source), en revanche nous ne connaissions pas la date du "fork".
Fan d'ATARI 2600, de THOMSON MO5-TO8 et d'ATARI ST
Mes articles : https://www.fxjavadevblog.fr/retro-programming/
Membre du groupe wide-dot.
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

Thomas m'a contacté pour la première fois en juillet 2013. Il a commencé par l'émulation du TO8D à partir de dcto8d version 2009.05.
A partir de là il a fait un énorme travail pour changer de version de SDL, corriger des bugs, faire des améliorations et émuler les autres machines Thomson. Aujourd'hui Theodore ne ressemble plus beaucoup à dcto8d, et diffère totalement de dcmoto qui a énormément évolué lui aussi.
Daniel
L'obstacle augmente mon ardeur.
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

A toutes fins utiles je donne le code utilisé par dcmoto pour l'émulation du SN76489.
La première version était très différente, inspirée de la version libretro trouvée sur internet.
Elle a été entièrement refaite sur le modèle de la version préconisée par Bentoc.
Le résultat est pratiquement identique, mais je trouve le code plus simple.

Code : Tout sélectionner

#include "dcmoto.h"

#ifdef EMULATION_SN76489

//DATAFORMATS
//- Update tone generator (2 bytes)
//b0=1
//b1-b3=reg address
//b4-b7=data
//b0=0
//b1=unused
//b2-b7=data
//- Update noise source (single byte)
//b0=1
//b1-b3=reg address
//b4=unused
//b5=FB
//b6-b7=shift
//- Update attenuator (single byte)
//b0=1
//b1-b3=reg address
//b4-b7=data


//global///////////////////////////////////////////////////////////////////////
int sn76489_freq[4];
int sn76489_vol[4];
int sn76489_out[4];
int sn76489_sign[4];
int sn76489_count[4];

int sn76489_tref;
int sn76489_seed;

int sn76489_sample;
int sn76489_start;
int sn76489_reg;

static int tablevolume[16] =
{0xff,0xcb,0xa1,0x80,0x65,0x50,0x40,0x33,
 0x28,0x20,0x19,0x14,0x10,0x0c,0x0a,0x00};

//Parite///////////////////////////////////////////////////////////////////////
static inline int parity(int val)
{
 val ^= val >> 8; val ^= val >> 4; val ^= val >> 2; val ^= val >> 1;
 return val & 1;
}

//Arret du synthetiseur////////////////////////////////////////////////////////
void SN76489_stop()
{
 sn76489_start = 0;
}

//Initialisations//////////////////////////////////////////////////////////////
void SN76489_init()
{
 int c;

 for(c = 0; c < 4; c++)
 {
  sn76489_vol[c] = 0;
  sn76489_out[c] = 0;
  sn76489_freq[c] = 0;
  sn76489_sign[c] = 0;
  sn76489_count[c] = 0;
 }
 sn76489_reg = 0;
 sn76489_tref = 0;
 sn76489_start = 1;
 sn76489_seed = 0x8000;
}

//calcul des echantillons//////////////////////////////////////////////////////
void SN76489_output()
{
 int c;
 int increment;
 extern int synthetiser;
 extern int samplerate;

 //pas de calcul si le synthetiseur SN76489 est absent ou inactif
 if((synthetiser & CODE_SN76489) == 0) return;
 if(!sn76489_start) return;

 //increment du compteur des canaux
 //determine la frequence du signal en sortie
 increment = 10000000 / samplerate / 42;

 //calcul du canal noise
 sn76489_sample = 0;
 sn76489_count[3] += increment;
 if(sn76489_count[3] & 0x400)
 {
  if(sn76489_sign[3]) //White
  sn76489_seed = (sn76489_seed >> 1) | (parity(sn76489_seed & 0x0003) << 14);
  else                //Periodic
  sn76489_seed = (sn76489_seed >> 1) | ((sn76489_seed & 1) << 14);
  if(sn76489_tref) sn76489_count[3] -= sn76489_freq[2];
  else sn76489_count[3] -= sn76489_freq[3];
 }
 if(sn76489_seed & 1) sn76489_out[3] += sn76489_vol[3];
 sn76489_sample += sn76489_out[3];
 sn76489_out[3] >>= 1;

 //calcul des troix canaux tone
 for(c = 0; c < 3; c++)
 {
  sn76489_count[c] += increment;
  if(sn76489_count[c] & 0x400)
  {
   if(sn76489_freq[c] > 1)
   {sn76489_sign[c] = !sn76489_sign[c]; sn76489_count[c] -= sn76489_freq[c];}
   else sn76489_sign[c] = 1;
  }
  if(sn76489_sign[c]) sn76489_out[c] += sn76489_vol[c];
  sn76489_sample += sn76489_out[c];
  sn76489_out[c] >>= 1;
 }
}

//reception d'une commande/////////////////////////////////////////////////////
void SN76489_command(int data)
{
 int c; //numero du canal concerne

 //si le sn76489 est arrete il doit être initialise
 if(sn76489_start == 0) SN76489_init();

 //deuxieme octet de commande
 if((data & 0x80) == 0)
 {
  c = sn76489_reg >> 1;  //numero du canal concernee
  sn76489_freq[c] = (sn76489_freq[c] & 0x0f) | ((data & 0x3f) << 4);
  return;
 }

 //premier octet de commande
 sn76489_reg = (data & 0x70) >> 4; //numero du registre concerne
 c = sn76489_reg >> 1;             //numero du canal concernee
 switch (sn76489_reg)
 {
  case 0: case 2: case 4: //tone
    sn76489_freq[c] = (sn76489_freq[c] & 0x3f0) | (data & 0xf);
    break;
  case 1: case 3: case 5: case 7: //volume
    sn76489_vol[c] = tablevolume[data & 0xf];
    break;
  case 6: //noise
    sn76489_sign[3] = (data & 4) >> 2;
    if((data & 3) == 3) {sn76489_freq[c] = sn76489_freq[2]; sn76489_tref = 1;}
    else {sn76489_freq[c] = 32 << (data & 3); sn76489_tref = 0;}
    if(sn76489_freq[c] == 0) sn76489_freq[c] = 1;
    sn76489_seed = 0x8000;
    break;
 }
}

#else // EMULATION_SN76489
int sn76489_sample = 0;
void SN76489_stop() {}
void SN76489_command(int data){}
#endif // EMULATION_SN76489
Daniel
L'obstacle augmente mon ardeur.
kirion
Messages : 344
Inscription : 22 sept. 2022 03:29

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par kirion »

Daniel a écrit : 29 janv. 2023 22:06 On ne peut pas envoyer du son 1 bit ou 6 bits vers le SN74489. Il sait uniquement générer du son en sortie à partir de commandes en entrée.

Dans l'ordinateur Thomson les quatre différentes sources de son : buzzer, cna, cassette audio et contrôleur externe sont mixées pour être envoyées au téléviseur ou au moniteur via la péritel. L'émulateur dcmoto réalise la même opération (sauf qu'il ne traite pas le son audio de la cassette) et envoie le résultat mixé vers la carte son du PC. Il est facile d'enregistrer cette sortie dans un fichier .wav pour l'analyser ensuite.

Je ne sais pas s'il est possible, à partir d'un fichier .wav, de composer automatiquement une séquence de commande pour la carte son. Ce n'est pas du domaine de l'émulation. Il faudrait ouvrir un nouveau sujet de discussion.
Daniel, sur ce site ils disent qu'on peut envoyer du son 1 bit, 4 bits ou 8 bits vers le SN74489. Est-ce que ça pourrait être implémenté dans Dcmoto pour envoyer le son des jeux existants vers l'émulation SN74489? Ils parlent bien des émulateurs qui peuvent utiliser le procédé décrit.

https://www.smspower.org/Development/SN ... MakesSound
Playing samples on the PSG

This is for the reference of those wishing to put sample playback in their demos, and for those whose sound core doesn't do voices. Emulator authors may wish to add implementation suggestions.

Sample playback is possible on the SN76489 and YM2413 FM chip, but only the former was used for that purpose in official games.

Sample playback makes use of a feature of the SN76489's tone generators: when the half-wavelength (tone value) is set to 1, they output a DC offset value corresponding to the volume level (i.e. the wave does not flip-flop). By rapidly manipulating the volume, a crude form of PCM is obtained.

(Note that this may be a feature of Sega's implementation of the SN76489. It does not appear in any Sega 8-bit code designed for older systems with the standard chip so it may need to be confirmed by experiment.)

Because the volume levels are non-linear, and only have four bits of resolution, it makes the quality of reproduction rather poor. Furthermore, there is no facility to stream data to the SN76489 so it must be done by the CPU; in almost all games this means the game is frozen while samples are played. In the few games that do some work during sample playback, the sample playback quality is usually made even worse.

Sega 8-bit games play their sampled audio in a few different ways:

Simple 4-bit PCM

A stream of 4-bit linear PCM data is read from ROM (packed two samples to a byte), and emitted as SN76489 attenuation commands to one or more of the tone channels. This results in a non-linear output which can make samples sound quieter than expected.

This can be ameliorated by pre-processing the data to account for the non-linear response; however, very few games do this.

1-bit PCM

A stream of 1-bit PCM data is read from ROM (packed eight samples to a byte), and emitted as either no, or maximum, SN76489 attenuation commands to one or more of the tone channels. This results in a faithful representation of the data, but the dynamic range of 1-bit audio is extremely poor so the result is not very good. Typically the samples seem to have been heavily amplified and clipped, resulting in loud samples.

Lookup for 8-bit PCM

A stream of 8-bit PCM data is read from ROM (one sample per byte) and used to look up a triplet of SN76489 attenuation commands from a table in ROM. These are emitted in close succession. By careful construction of the lookup table, the commands are able to address a large number of volume levels by combining the non-linear volumes.

During the transition from one sample to another, this can produce unwanted artefacts because the intermediate total attenuation may not lie between the start and end points. For example, transitioning from attenuations 4,0,0 (total output level 79.9%) to 2,1,0 (total output level 80.1%) may temporarily be in the state 2,0,0 (total output level 87.7%). This can be avoided by minimising the transition time, but seems to still produce noise.

Methods not found in games

The 1-bit approach could be used for Pulse-density modulation, which promises much higher clarity. At very high sampling rates it may exceed the quality of PCM, thanks to noise shaping, but at the highest rates achievable on the Master System/Game Gear the result is quite noisy.
Pulse Width Modulation is a special case of Pulse Density Modulation and is not very useful.
Preprocessing of the data to have a low-frequency DC offset can allow moving the range of the high-frequency waveform into the area of the non-linear response where the precision is greater. This can particularly help with samples with a large dynamic range. See this post by blargg for examples and a program to do the conversion.
The issues with noise in the 8-bit lookup method could be ameliorated by instead storing a 12-bit stream of attenuation command triplets, each optimised to avoid out-of-range transitions from the previous sample.
FM (OPLL) Playback: while possible, wasn't used in commercial games. To get the constant DC offset used in this technique, ideally you have to choose an instrument and freeze the synthesis when the wave reaches maximum amplitude by setting the frequency to 0. With the DC Ofset created the amplitude can be modulated according to the original PCM data. Additional FM channels may be used to achieve higher playback resolutions, as the FM chip is slower than the PSG.

Sample preprocessing

The results of sample playback can be improved by better preprocessing of the data. It should at least be normalised to 100% to allow the best use of the output range. The dynamic range can be compressed to make the sample sound louder, and make quiet sounds more reproducible. Quiet parts should be silenced or offset to avoid a 1-bit noise floor in a 4-bit sample.

Uniformity of playback

In almost all cases, the underlying audio data will be based on a uniform sampling rate. If it is played back at a non-uniform rate, due to branches in the code (for example to retrieve the next byte for <8bit samples), it may produce unwanted effects. In practice, however, this seems not matter very much and many games have some small non-uniformity. It is usually possible to cancel out the branching effect by adding some time-wasting opcodes to the faster branches.

Sampling rates

For all sample playback methods, there is generally an improvement in quality as the sampling rate goes up; but this has a high cost for ROM space. No game plays samples as fast as the CPU can go, they all include some sort of busy wait to limit the rate.

The lowest quality audio seen in games is around 4kHz at 1 bit, which can fit 32.8s of audio in 16KB. The highest is around 21kHz at 4 bits, which can fit 1.6s of audio in 16KB.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par __sam__ »

Oui évidemment que si le SN764889 a une période de 1 cycle, il envoie un signal continu dont on peu changer le volume, ce qui en fait une sorte de CNA avec 4 bits (il y a 16 niveaux de volumes). Mais cela ne sert à rien puisque le Thomson a déjà un CNA de 6 bits (64 niveaux).

Par contre ca n'est pas de l'envoi de son 1 ou 4 bits, mais de (re)production de son échantillonné. Tout se passe dans le logiciel émulé pas dans l'émulateur. Il faut écrire le code 6809 pour ca et ne pas modifier l'émulateur qui ne fait que reproduire le fonctionnement du SN76489 sans rien de plus.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

J'ai beau tourner la question dans tous les sens, je ne la comprends pas. Dans l'ordinateur Thomson les sons provenant du buzzer et du CNA sont mixés avec ceux provenant du SN76489. Si on les remplace par une simulation par le SN76489 avec la méthode indiquée par smspower le résultat sera plus mauvais que l'original, et en plus on monopolisera deux canaux du SN76489 qui ne pourront plus être utilisés pour la génération d'autres sons.

Le seul intérêt pourrait être, pour les ordinateurs sans extension musique et jeux, de simuler le CNA avec le SN76489. Mais pour cela il faudrait modifier tous les programmes. C'est un travail considérable et il faudrait être fou pour s'y lancer. Il est beaucoup plus facile d'acheter ou de construire une extension musique et jeux.

Dans dcmoto la question ne se pose pas: Il suffit de cocher une case dans les options pour activer le CNA s'il n'est pas intégré à l'ordinateur émulé.
Daniel
L'obstacle augmente mon ardeur.
Avatar de l’utilisateur
hlide
Messages : 3469
Inscription : 29 nov. 2017 10:23

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par hlide »

Moi aussi je ne comprends plus la demande initiale. Je croyais qu'il s'agissait de mixer au niveau hardware (et donc in fine dans l'émulateur aussi), deux sources sonores en une seule. Je n'ai pas été vérifié au niveau du schéma si la possibilité existait déjà. Je sais juste que certaines versions du SN76xxx permettent de le faire et que je connais quelques machines qui le font.

Par contre, je ne vois pas l'intérêt de faire du 1/4/8-bit PCM par l'émulateur puisqu'à priori il n'y a pas spécialement d'ajout de hardware pour le faire (pure programmation du SN), donc je suis perplexe par la demande et réponse donnée qui n'a pas vraiment de sens dans ce contexte.
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [DCMOTO] Emulation du générateur de son SN76489

Message par Daniel »

hlide a écrit :Je n'ai pas été vérifié au niveau du schéma si la possibilité existait déjà
La possibilité n'existe pas. Les sorties du CNA, du buzzer, de l'audio cassette et des extensions sont mixées et envoyées vers la péritel et la sortie audio RCA du TO8. Il n'y a aucun moyen de récupérer la sortie du CNA ou du buzzer sur le bus d'extension pour l'envoyer au SN76489.

Et, encore une fois, pourquoi mixer dans le SN76489 alors que l'ordinateur fait cela très bien ? C'est complètement inutile et nuisible, car la sortie du SN76489 sera à nouveau mixée avec le CNA et le buzzer. On ne peut pas l'empêcher par soft, il faudrait modifier la carte mère.

La première question à se poser, à laquelle je ne trouve pas de réponse : quel est l'objectif final ?
Quand cet objectif sera clair, nous trouverons les bonnes solutions.
Daniel
L'obstacle augmente mon ardeur.
Répondre