/**************************************************************************
* Programma per la sintesi sonora, ideato e creato da Giuseppe Ligorio    *
* Il programma una volta fatto partire, esegue e fa ascoltare l'esempio 4 *
* modificare la chiamata nel main() per ascoltare gli altri.              *
* La sintesi risulta molto lenta, vedremo di provvedere con qualche       *
* routine in assembler                                                    *
**************************************************************************/

/* Inclusione strutture e definizioni di riferimento */
#include <proto/exec.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <math.h>
#include <stdio.h>

#define SINUSOIDE 1

/* Lunghezza dello strumento */
#define LUNG 10000

/* Definizione struttura per la sintesi additiva; vengono inseriti il numero di
   dorme d'onda da addizionare e i puntatori alle zone di memoria dove sono contenute. */
typedef struct AddSynthData
{
  int numwave;
  BYTE *data[16];
};

/* Forma d'onda base per l'oscillatore. */
BYTE base1[257];
struct AddSynthData AddData1;
/* Forma d'onde utilizzate: fondamentale e 3 armoniche, Gen e' utilizzato per
   i dati della modulante. */
BYTE Fond[LUNG],Arm1[LUNG],Arm2[LUNG],Arm3[LUNG],Gen[100];
/* Periodo di campionamento e di esecuzione; pcampionamento <= 2*pesecuzione. */
double pcampionamento = 0.0000125,pesecuzione = 0.000125;
/* Puntatore al buffer di esecuzione in CHIP. */
BYTE *suono;

/* Definizione di prototipi. */
void GeneraBaseStandard(BYTE *,int);
void Oscill(BYTE *,BYTE *,ULONG,double,double);
void AddSynth(struct AddSynthData *,BYTE *,ULONG);
void ModAmpSynth(BYTE *,ULONG,BYTE *,ULONG,double,double,BYTE *);
void Esempio1(void);
void Esempio2(void);
void Esempio3(void);
void Esempio4(void);

/* Routine che genera la base standard per l'oscillatore, prevista solo la sinu-
   soidale. */
void GeneraBaseStandard(base,tipo)
register BYTE *base;
register int tipo;
{
  register ULONG k;
  
  if (tipo == SINUSOIDE)
  {
    for (k=0; k<256; k++)
      *(base+k) = (BYTE)(127.0*sin((double)k*PI/128.0));
  }
  *(base + 256) = 0;
  return;
}

/* Oscillatore digitale. wave e' la forma d'onda di base da cui di genera,
   ripetendosi, l'onda definitiva memorizzata in data; lung e' la lunghezza
   che si vuole ottenere; pcam e pesec sono rispettivamente il periodo di
   campionamento e di esecuzione, considerati fissi per tutte le sintesi. */
void Oscill(wave,data,lung,pcam,pesec)
register BYTE *wave,*data;
register ULONG lung;
double pcam,pesec;
{
  register ULONG k,ps;
  register double inc,psreal;
  register BYTE v1,v2;
  
  inc = 256.0/(pesec/pcam); /* numero campioni per periodo */
  psreal = inc / 2.0;
  for (k=0; k<lung; k++)
  {
    ps = (ULONG)psreal;
    v1 = *(wave + ps);
    v2 = *(wave + ps + 1);
    *data++ = (BYTE)(((double)v2-(double)v1)*(psreal-(double)ps)+(double)v1);
    if ((psreal += inc) >= 256.0) psreal -= 256.0;
  }
  return;
}

/* Sintesi additiva. datain e' il puntatore alla struttura dati del tipo
   AddSynthData contenente i puntatori delle onde; dataout e' il puntatore
   al buffer d'uscita, lung e' la lunghezza in byte delle onde.*/
void AddSynth(datain,dataout,lung)
register struct AddSynthData *datain;
register BYTE *dataout;
register ULONG lung;
{
  register ULONG k,cic;
  register LONG value;
  
  for (k=0; k<lung; k++)
  {
    value = 0;
    for (cic=0; cic < datain->numwave; cic++)
      value += (LONG)*(datain->data[cic]+k);
    *dataout++ = (BYTE)(value / (LONG)(datain->numwave));
  }
  return;
}

/* Sintesi per modulazione d'ampiezza, portante e' il puntatore alla portante
   lung e' la lunghezza della portante; modulante e' il puntatore alla modulante
   modlung e' il numero di campioni della modulante e modper, il periodo in
   secondi della modulante; pcam e' il periodo di campionamento, sempre fisso e
   dataout e' il puntatore alla zona di memoria per i dati finali.
   Nota: portante e dataout possono essere lo stesso indirizzo */
void ModAmpSynth(portante,lung,modulante,modlung,modper,pcam,dataout)
register BYTE *portante;
register ULONG lung;
register BYTE *modulante;
register ULONG modlung;
double modper,pcam;
register BYTE *dataout;
{
  register ULONG k,ps;
  register LONG mod;
  register double inc,psreal;
  register BYTE v1,v2;
  
  inc = modlung/(modper/pcam);
  psreal = 0.0;
  for (k=0; k<lung; k++)
  {
    ps = (ULONG)psreal;
    v1 = *(modulante + ps);
    v2 = *(modulante + ps + 1);
    mod = (LONG)(((double)v2-(double)v1)*(psreal-(double)ps)+(double)v1+128.0);
    mod = (mod*(LONG)(*portante++))/255;
    if (mod > 127) *dataout++ = 127;
    else if (mod < -128) *dataout++ = -128;
    else *dataout++ = (BYTE)mod;
    if ((psreal += inc) >= (double)modlung)
      psreal -= (double)modlung;
  }
  return;
}

/* Primo esempio, genera una semplice onda sinusoidale (simile a quella di un
   piano) a frequenza di 800 Hz (se viene rispettata le frequenza di esecuzione) */
void Esempio1()
{
  GeneraBaseStandard(base1,SINUSOIDE);
  Oscill(base1,suono,LUNG,pcampionamento,pesecuzione);
  return;
}

/* Secondo esempio, genera un'onda sinusoidale di 800 Hz con due armoniche, il
   suono in tale modo si arricchisce. */
void Esempio2()
{
  GeneraBaseStandard(base1,SINUSOIDE);
  Oscill(base1,Fond,LUNG,pcampionamento,pesecuzione);
  Oscill(base1,Arm1,LUNG,pcampionamento/2.0,pesecuzione);
  Oscill(base1,Arm2,LUNG,pcampionamento/4.0,pesecuzione);
  AddData1.numwave = 3;
  AddData1.data[0] = Fond;
  AddData1.data[1] = Arm1;
  AddData1.data[2] = Arm2;
  AddSynth(&AddData1,suono,LUNG);
  return;
}

/* Terzo esempio, onda sinusoidale con modulazione d'ampiezza (si ottiene un effetto
   di vibrato in volume). */
void Esempio3()
{
  GeneraBaseStandard(base1,SINUSOIDE);
  Oscill(base1,Fond,LUNG,pcampionamento,pesecuzione);
  *Arm1 = 0;
  *(Arm1+1) = 127;
  *(Arm1+2) = 0;
  ModAmpSynth(Fond,LUNG,Arm1,2,0.03125,pcampionamento,suono);
  return;
}

/* Quarto esempio: onda sinusoidale 800 Hz con 3 armoniche e modulazione
   d'ampiezza. */
void Esempio4()
{
  GeneraBaseStandard(base1,SINUSOIDE);
  Oscill(base1,Fond,LUNG,pcampionamento,pesecuzione);
  Oscill(base1,Arm1,LUNG,pcampionamento/2.0,pesecuzione);
  *Gen = 60;
  *(Gen+1) = 60;
  ModAmpSynth(Arm1,LUNG,Gen,1,0.03125,pcampionamento,Arm1);
  Oscill(base1,Arm2,LUNG,pcampionamento/4.0,pesecuzione);
  *Gen = 15;
  *(Gen+1) = 15;
  ModAmpSynth(Arm2,LUNG,Gen,1,0.03125,pcampionamento,Arm2);
  Oscill(base1,Arm3,LUNG,pcampionamento/4.0,pesecuzione);
  *Gen = 30;
  *(Gen+1) = 30;
  ModAmpSynth(Arm3,LUNG,Gen,1,0.03125,pcampionamento,Arm3);
  AddData1.numwave = 3;
  AddData1.data[0] = Fond;
  AddData1.data[1] = Arm1;
  AddData1.data[2] = Arm2;
  AddData1.data[2] = Arm3;
  AddSynth(&AddData1,suono,LUNG);
  *Gen = 30;
  *(Gen+1) = 127;
  *(Gen+2) = 30;
  ModAmpSynth(suono,LUNG,Gen,2,0.03125,pcampionamento,suono);
  return;
}

void main()
{
  char nome[80];
  
  if ((suono = (BYTE *)AllocMem(LUNG,MEMF_CHIP|MEMF_CLEAR)) == NULL)
  {
    printf("Errore non posso allocare buffer.\n");
    Exit(10);
  }

  printf("Sintesi...\n");
  Esempio4();
  printf("Esecuzione - \n");

  /* Orrore! Occorre sempre utilizzare l'audio.device per l'esecuzione sonora
     ma pilotando direttamente l'hardware si risolve tutto in 5 istruzioni
     (rispettoso delle regole Commodore si, ma mica scemo). */
  /* Puntatore al buffer contenente il suono. */
  *(ULONG *)(0xDFF0A0) = (ULONG)suono;
  /* Lunghezza del suono; si indicano il numero di word per cui occorre dividere per 2. */
  *(UWORD *)(0xDFF0A4) = LUNG >> 1;
  /* Periodo: 420 corrisponde a 8300 Hz circa. */
  *(UWORD *)(0xDFF0A6) = 420;
  /* Volume: massimo. */
  *(UWORD *)(0xDFF0A8) = 64;
  /* Partenza canale DMA Audio num. 0 */
  *(UWORD *)(0xDFF096) = 0x8201;
  /* Attesa pressione RETRUN */
  gets(nome);
  /* Stop canale audio. */
  *(UWORD *)(0xDFF096) = 0x0001;
  FreeMem(suono,LUNG);
}
