Main Menu

ZX SOUND

Started by baggey, November 09, 2012, 11:59:48 AM

Previous topic - Next topic

baggey

Im looking for someone who has knowledge of the AY sound chip!

I know that playbasic has playsound for .wav files.

I really dont have clue how im going to implement this in my emulator!?

If anyone has info of pin inputs for pulse duration etc. We may be able to create a .wav file from within playbasic. I hope! Which can be played.

In Spectrum basic "Beep 1,10" would create a tone.

Early days yet but ive been thinking about this whilst typing up the op-codes for the z80 emulator!

Baggey
Jesus was only famous because of his dad

baggey

#1
Can playbasic generate its own sounds?

Ie Could we generate some code. Then name it as .wav!?

But im, at a loss on the make up of the .wav.  ???

Baggey
Jesus was only famous because of his dad

baggey

#2
Im starting to get some info together hopefully to emulate SOUND.

1)   The frequency of AY-3-8912 is half the ZX Spectrum frequency
2)   Envelope frequency is 1/(256*envelop_period) of AY-3-8912 frequency
3)   Tone frequency is 1/(16*tone_period) of AY-3-8912 frequency
4)   Noise frequency is 1/(16*noise_period) of AY-3-8912 frequency
5)   Period = 0 is the same as period = 1. However, this does NOT apply to the Envelope period. In that case, period = 0 is half as period = 1
6)   For period of tones, noise and envelope, the chip counts up from 0 until the counter becomes greater or equal to the period. This is an important when the program is rapidly changing the period to modulate the sound.
7)   The 8912 has three outputs, each output is the mix of one of the three tone generators and of the (single) noise generator. The two are mixed BEFORE going into the DAC. The formula to mix each channel is: (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). This means that if both tone and noise are disabled, the output is 1, not 0, and can be modulated changing the volume.
8 )   If the register 13 (envelope shape) is written the envelope restart the cycle.
9)   This seems the only well working noise generators. It is to be executed each noise period:

static noise = 1;

if( ( noise & 1 ) ^ ( ( noise & 2 ) ? 1 : 0 ) ) NoiseOn = ! NoiseOn;

noise |= ( ( noise & 1 ) ^ ( ( noise & 4 ) ? 1 : 0 ) ) ? 0x20000 : 0;

noise >>= 1;

10)   The volume should use these values:

const int levels[16] = {
0x0000, 0x0385, 0x053D, 0x0770,
0x0AD7, 0x0FD5, 0x15B0, 0x230C,
0x2B4C, 0x43C1, 0x5A4B, 0x732F,
0x9204, 0xAFF1, 0xD921, 0xFFFF

11) Other information are in the AY-3-8912 manual (30-OCT-1999)and they are all right.

All the various AY 891x chips behave the same. The differences is the number of I/O channels available. The YM2149 has double the resolution on one of the registers, but is otherwise the same as the AY 8910.

The frequency of the AY is typically half the frequency of the clock, but that varies by machine:

16K, 48K, TC2048, TC2068: 3.5Mhz Z80 / 1.75Mhz AY

TS2068: 3.528Mhz Z80 / 1.764Mhz AY

128: 3.5469Mhz Z80 / 1.77345Mhz AY

Here is some code in pascal. I dont now how the instructions emulate the sound thou?

PlayBASIC Code: [Select]
var
Noise:packed record
case boolean of
True: (Seed:longword);
False:(Low:word;
Val:dword);
end;

function NoiseGenerator(Seed:integer):integer;
asm
shld edx,eax,16
shld ecx,eax,19
xor ecx,edx
and ecx,1
add eax,eax
and eax,$1ffff
inc eax
xor eax,ecx
end;

procedure ResetAYChipEmulation;
begin
...
Noise.Seed := $ffff;
Noise.Val := 0;
...
end;

{for each noise generator tick do}
...
Noise.Seed := NoiseGenerator(Noise.Seed);
...

Output is Noise.Val (0 or 1)




Schematic of the chip :-

Jesus was only famous because of his dad

baggey

#3
I tried to keep this in the same post but it wouldn't let me so.

PlayBASIC Code: [Select]
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package machine;

import java.util.Arrays;
import snapshots.AY8912State;

/**
*
* @author jsanchez
*/

public final class AY8912 {

private static final int FineToneA = 0;
private static final int CoarseToneA = 1;
private static final int FineToneB = 2;
private static final int CoarseToneB = 3;
private static final int FineToneC = 4;
private static final int CoarseToneC = 5;
private static final int NoisePeriod = 6;
private static final int Mixer = 7;
private static final int AmplitudeA = 8;
private static final int AmplitudeB = 9;
private static final int AmplitudeC = 10;
private static final int FineEnvelope = 11;
private static final int CoarseEnvelope = 12;
private static final int EnvelopeShapeCycle = 13;
private static final int IOPortA = 14;
private static final int IOPortB = 15;
// Other definitions
private static final int TONE_A = 0x01;
private static final int TONE_B = 0x02;
private static final int TONE_C = 0x04;
private static final int NOISE_A = 0x08;
private static final int NOISE_B = 0x10;
private static final int NOISE_C = 0x20;
private static final int ENVELOPE = 0x10;
// Envelope shape bits
private static final int HOLD = 0x01;
private static final int ALTERNATE = 0x02;
private static final int ATTACK = 0x04;
private static final int CONTINUE = 0x08;
// Channel periods
private int periodA, periodB, periodC, periodN;
// Channel period counters
private int counterA, counterB, counterC;
// Channel amplitudes
private int amplitudeA, amplitudeB, amplitudeC;
// Noise counter & noise seed
private int counterN, rng;
private int envelopePeriod, envelopeCounter;

/* Envelope shape cycles:
C AtAlH
0 0 x x \_______

0 1 x x /|______

1 0 0 0 \|\|\|\|

1 0 0 1 \_______

1 0 1 0 \/\/\/\/
______
1 0 1 1 \|

1 1 0 0 /|/|/|/|
_______
1 1 0 1 /

1 1 1 0 /\/\/\/\

1 1 1 1 /|______

The envelope counter on the AY-3-8910 has 16 steps.
*/

private boolean Continue, Attack;
// Envelope amplitude
private int amplitudeEnv;
private int maxAmplitude;
// AY register index
private int addressLatch;
// AY register set
private int regAY[] = new int[16];
// Output signal levels (thanks to Matthew Westcott)
// http://groups.google.com/group/comp.sys.sinclair/browse_thread/thread/
// fb3091da4c4caf26/d5959a800cda0b5e?lnk=gst&q=Matthew+Westcott#d5959a800cda0b5e
private static final double volumeRate[] = {
0.0000, 0.0137, 0.0205, 0.0291, 0.0423, 0.0618, 0.0847, 0.1369,
0.1691, 0.2647, 0.3527, 0.4499, 0.5704, 0.6873, 0.8482, 1.0000
};
// Real (for the soundcard) volume levels
private int[] volumeLevel = new int[16];
private int[] bufA;
private int[] bufB;
private int[] bufC;
private int pbuf;
private int FREQ;
private double step, stepCounter, stepRate;
// Tone channel levels
private boolean toneA, toneB, toneC, toneN;
private boolean disableToneA, disableToneB, disableToneC;
private boolean disableNoiseA, disableNoiseB, disableNoiseC;
private boolean envA, envB, envC;
private int volumeA, volumeB, volumeC;
// private int lastA, lastB, lastC;
private int audiotstates, samplesPerFrame;
private MachineTypes spectrumModel;

AY8912() {
maxAmplitude = 8192;
for (int idx = 0; idx < volumeLevel.length; idx++) {
volumeLevel[idx] = (int) (maxAmplitude * volumeRate[idx]);
// System.out.println(String.format("volumeLevel[%d]: %d",
// idx, volumeLevel[idx]));
}
}

public AY8912State getAY8912State() {
AY8912State state = new AY8912State();
state.setAddressLatch(addressLatch);
int regs&#91;] = new int[16];
System.arraycopy(regAY, 0, regs, 0, regAY.length);
state.setRegAY(regs);
return state;
}

public void setAY8912State(AY8912State state) {

int&#91;] ay = state.getRegAY();
for (int reg = 0; reg < 16; reg++) {
addressLatch = reg;
writeRegister(ay[reg]);
}

addressLatch = state.getAddressLatch();
}

public void setSpectrumModel(MachineTypes model) {
if (spectrumModel != model) {
spectrumModel = model;
reset();
}

if (samplesPerFrame != 0) {
step = (double) spectrumModel.tstatesFrame / (double) samplesPerFrame;
stepRate = step / 16.0;
Login required to view complete source code


And another snippet of code

PlayBASIC Code: [Select]
/*
* Audio.java
*
* 2009-2011 José Luis Sánchez
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/

package machine;

import configuration.AY8912Type;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.*;

class Audio {
private int samplingFrequency;
private SourceDataLine line;
private DataLine.Info infoDataLine;
private AudioFormat fmt;
private SourceDataLine sdl;
// Buffer de sonido para el frame actual, hay más espacio del necesario.
private final byte&#91;] buf = new byte[4096];
private final int&#91;] beeper = new int[1024];
// Buffer de sonido para el AY
private final int&#91;] ayBufA = new int[1024];
private final int&#91;] ayBufB = new int[1024];
private final int&#91;] ayBufC = new int[1024];
private int bufp;
private int level, lastLevel;
private int audiotstates;
private int samplesPerFrame, frameSize, bufferSize;
private int soundMode;
private float timeRem, spf;
private MachineTypes spectrumModel;
private boolean enabledAY;
private AY8912Type settings;
private AY8912 ay;

Audio(AY8912Type ayConf) {
settings = ayConf;
line = null;
samplingFrequency = 32000;
}

synchronized void open(MachineTypes model, AY8912 ay8912, boolean hasAY, int freq) {
samplingFrequency = freq;
soundMode = settings.getSoundMode();
if (soundMode < 0 || soundMode > 3)
soundMode = 0;
// System.out.println("Selected soundMode " + soundMode);

int channels = soundMode > 0 ? 2 : 1;

if (line == null) {
try {
fmt = new AudioFormat(samplingFrequency, 16, channels, true, false);
// System.out.println(fmt);
infoDataLine = new DataLine.Info(SourceDataLine.class, fmt);
sdl = (SourceDataLine) AudioSystem.getLine(infoDataLine);
} catch (Exception excpt) {
Logger.getLogger(Audio.class.getName()).log(Level.SEVERE, null, excpt);
}

enabledAY = hasAY;
timeRem = 0.0f;
samplesPerFrame = samplingFrequency / 50;
frameSize = samplesPerFrame * 2 * channels;
if (model != spectrumModel) {
spectrumModel = model;
ay8912.setSpectrumModel(spectrumModel);
}
spf = (float) spectrumModel.getTstatesFrame() / samplesPerFrame;
audiotstates = bufp = level = lastLevel = 0;
if (soundMode > 0) {
ay8912.setMaxAmplitude(12288); // 21844
} else {
ay8912.setMaxAmplitude(8192); // 16384
}

bufferSize = frameSize * 5;

try {
sdl.open(fmt, bufferSize);
sdl.start();
line = sdl;
} catch (LineUnavailableException ex) {
Logger.getLogger(Audio.class.getName()).log(Level.SEVERE, null, ex);
}

ay8912.setBufferChannels(ayBufA, ayBufB, ayBufC);
ay8912.setAudioFreq(samplingFrequency);
ay8912.startPlay();
ay = ay8912;
}
}

synchronized void close() {
if (line != null) {
line.drain();
line.stop();
line.close();
line = null;
}
}

synchronized void updateAudio(int tstates, int value) {
tstates = tstates - audiotstates;
audiotstates += tstates;
float time = tstates;

synchronized (beeper) {
if (time + timeRem > spf) {
level += ((spf - timeRem) / spf) * value;
time -= spf - timeRem;
lastLevel = (lastLevel + level) >>> 1;
beeper[bufp++] = lastLevel;
} else {
timeRem += time;
level += (time / spf) * value;
return;
}

while (time > spf) {
lastLevel = (lastLevel + value) >>> 1;
beeper[bufp++] = lastLevel;
time -= spf;
}
}

// calculamos el nivel de sonido de la parte residual de la muestra
// para el próximo cambio de estado
level = (int) (value * (time / spf));
timeRem = time;
}

private void flushBuffer(int len) {

if (line != null) {
int available = line.available();
if (available < (frameSize >>> 1)) {
return;
}

synchronized (buf) {
line.write(buf, 0, len);
if (available == bufferSize) {
line.write(buf, 0, len);
Login required to view complete source code


If anyone understands JAVA and has Knowledge of music. You maybe able to relate or translate to PB!

I will add your name to the ABOUT section were It will appear in the final version of the Spectrum Playbasic emulator!

HERE'S THE MUSIC THEME OF COBRA.  http://www.youtube.com/watch?v=NIsgdtyFhKE


Kind regards Baggey


Jesus was only famous because of his dad

baggey

#4
Hi Kevin,

Ive been sent a snippet of code and information. From someone, on another forum who say's its EASY?

QuoteHi Baggey,

Congrats on writing an emulator!

Emulating the sound is quite easy. Firstly, you need a WAVE buffer that you can store sound samples to. Preferably, a circular buffer system that will continuously loop. Hopefully, playbasic provides this.

The 48k spectrum is a 50Hz (50 ULA frames per second) machine and if you're going to play high quality stereo sound at 44.1KHz, you roughly need 44100/50*= 882 sound samples per frame. To do this, we need to figure out a way to sample the BEEPER every few t-states so that we can keep up with this rate. How?

Since there are 69888 t-states in 1 frame, we need to sample the BEEPER every 69888/882 = *~79 t-state.

However, if you were to simply sample the BEEPER every t-state you will get unrefined and harsh sound because the sampling isn't discrete enough. To overcome this we do sub-sampling to smoothen this out. All that means is that instead of sampling the BEEPER every 79 t-states, we sample it after every instruction execution. Then once we average it every 79 t-states and it to our sound buffer. Something like this:

Code:
PlayBASIC Code: [Select]
//soundTStatesToSample = 79
Frame emulation loop:

oldTStates = totalTStates; this i think is were im adding up t-states until we get exetstates=79

ExecuteOneInstruction(); This is after ever execute= true; Which is in my op_code goto tables

deltaTStates = totalTStates - oldTStates;

timeToOutSound += deltaTStates; don't know what a += means?

//soundOut is set during OUT
averagedSound += soundOut;

//Update sound every 79 tstates
if (timeToOutSound >=soundTStatesToSample)
{
averagedSound /= soundCounter; don't know what a /= means?

while (timeToOutSound >= soundTStatesToSample)
{
AddSample2Buffer(averagedSound);
}

//Re-adjust for next frame
timeToOutSound -= soundTStatesToSample; don't know what a -= means?
};
}


soundOut is set during any OUT to the speccy like so:
Code:

PlayBASIC Code: [Select]
                    int beepVal = val & (EAR_BIT); //0x10       Ear_Bit is either going to be 1 or 0 in the port out(254), Which we look at every 79 exetstates.
if (beepVal != lastSoundOut)
{
if ((beepVal) == 0) don't know what a == means?
{
soundOut = MIN_SOUND_VOL;// 0.0f;
}
else
{
soundOut = MAX_SOUND_VOL; //0.5f maybe
}

lastSoundOut = beepVal;
}


I hope that makes sense!
__________________


A link to some assembler code. I might be showing my lack of understanding here, But. I am learning. I think i read some were that Playbasic can use .dll's. This i presume is a Machine code file?

QuoteOriginally Posted by baggey  
Playbasic Compiles and creates stand alone .EXE's.

QuoteYour the second person who has said the sound is easy! I dont know what a looping buffer is or how i would even begin to send samples to it?

QuoteA looping buffer is a ... well, it's a section of memory that is played by your sound library. When playback reached the end of the buffer, it starts at the beginning again. You write samples into that buffer at a point behind the playing position, so that when it loops around it gets played. You check the state of the beeper (port $FE) every 79Tstates - it will be 0 or 1. You write that value (scaled up to the size of your sample's bit resolution - say multiply by 32767 for a 16bit sample buffer) in sequence, advancing as you go.

Most emulators buffer about one frame's worth of samples before writing them out to the looping sample, and then idle until that frame's sample starts playing. This gets you perfect sound synchronisation.

D.

Could you plz... Have a look and give me some ideas or pointers. If possible? :-*

Baggey
Jesus was only famous because of his dad

baggey

Hi me again.

Can we in playbasic. Write values into the WAV that is playing? and have it automatically loop?
Can we find where the current playing position is?

Bit 7 of out port 254 is what i need to check every 79 or 74 t states!

Kind regards baggey
Jesus was only famous because of his dad

kevin

QuoteWrite values into the WAV that is playing? and have it automatically loop?

  nope.


baggey

Could a dll be created to achive the effect?

Baggey
Jesus was only famous because of his dad

kevin


monkeybot

If my memory serves me right it has something to do with a windows licensing issue.(I think)

You can use dll`s in pub,sounds like you need to find a custom sound DLL.

kevin


There's any number of sound libraries out there today, this for example uses a wrapped version of FMOD, since the app needed to read/set the playback position.    Been meaning to make a general slib wrapper for it, but haven't found the time.     

monkeybot


baggey

Yes Please Do!!  ;D

I Think this is the sort of thing needed for "The Spectrum Playbasic"

Its an out there idea. But have new commands within PLAYBASIC to do this  ???

Kind regards Baggey
Jesus was only famous because of his dad

kevin


Quote
Its an out there idea. But have new commands within PLAYBASIC to do this

  Wrapping  dll functions makes them an internal command.   


baggey

#14
Hi Kevin,

How would i achieve this? I dont know the meaning of "wraping" or the idea in the first place!

Do we have any examples etc? Id like to know how, i can use a .Dll and implement it into playbasic ;D

Kind regards Baggey
Jesus was only famous because of his dad