Subject: Re: Aw: Re: [stella] The future of Stella (i.e. debugger needed) From: Stephen Anthony <stephena@xxxxxxxxxxxxxxxxxxxxx> Date: Thu, 20 May 2004 10:33:32 -0230 |
On May 18, 2004 10:44 am, Thomas Jentzsch wrote: > Steven wrote: > > Yes, both are GPL. But right now, z26 is in x86 assembly, and > > Stella is in C++. I don't know enough ASM to do the conversion. > > Besides, it's not just the sound code itself, but timing and > > resampling issues. > > > > I really need someone who knows more about sound programming than I > > do to look at it . Perhaps they'd find the problem to be quite > > easy to fix. > > > > Heck, even if someone took a look at the current way of doing it > > and made some recommendations, that could help. I could probably > > do the actual coding; it's just the underlying timing/resampling > > *algorithm* that's eluded me. > > I suppose we are just to lazy (I am!) to download the whole package > (incl. compiler etc.) and search for the code. > > So maybe if you could just post the code with the problem (and maybe > the link to the matching z26 code), someone like Manuel or me could > use their Assembler knowledge to find the differences. OK, to all those that asked for it, here's the first problem. I'm using the TIAsound code which is essentially still the same as that which Ron Fries released years ago. According to my sources, the sound code in z26 is no longer really the same as Ron Fries code, and as had extensive modifications. So if someone were to port *that* code to C/C++ (or any high-level language), it would be a great help. The main problem I'm having is that (1) not enough samples are being produced per frame to account for Pitfall2 and Quadrun (digital sound), and (2) in Windows, the sound is dropping out because the buffer size I use, and If I use a larger buffer size, the sound changes pitch. One partial solution is in the attached 'SoundSDL.cxx' file. In the SoundSDL::set() method, in the section '#if defined(DIGITAL_SOUND)', we see there is a way to generate the correct # of samples. This is the way Stella 1.3 worked. The problem with this approach is that now, *too many* samples are created per second. If I let the SDL audio callback process them as it needs them, the sounds come out correct, but we lose a/v sync, since the audio callback falls behind the video rendering. My guess would be that some sort of resampling has to be done, so that the # of samples created is precisely what SDL wants, but it still includes all the sample information as before (a resampling compression algorithm, if you will). My guess is this is what z26 does. So, I'm including the following files from Stella: 1) SoundSDL.cxx - this one is the main sound dispatching code. It takes requests from other parts of the system and sends them to the TIAsound functions. It also maintains a queue. Some parts of this class are quite similar to 'soundq.asm' from z26. 2) TIAsound.c - the TIA sound routines, originally from Ron Fries. This is what 'tiasnd.asm' from z26 is based on. And from z26: 1) soundq.asm - creates a sound queue for the SDL audio callback. Always keeps the queue full to capacity. I suspect there is resampling going on here. 2) tiasnd.asm - The actual sound generation routines based on code from Ron Fries. Any help is appreciated. I can try to provide more info if required. Thanks, Steve
//============================================================================ // // SSSS tt lll lll // SS SS tt ll ll // SS tttttt eeee ll ll aaaa // SSSS tt ee ee ll ll aa // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" // SS SS tt ee ll ll aa aa // SSSS ttt eeeee llll llll aaaaa // // Copyright (c) 1995-2002 by Bradford W. Mott // // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // // $Id: SoundSDL.cxx,v 1.10 2004/04/27 00:50:52 stephena Exp $ //============================================================================ #include <SDL.h> #include "TIASound.h" #include "Serializer.hxx" #include "Deserializer.hxx" #include "System.hxx" #include "SoundSDL.hxx" //#define DIGITAL_SOUND // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) : myCurrentVolume(SDL_MIX_MAXVOLUME), myFragmentSize(fragsize), myIsInitializedFlag(false), myIsMuted(false), mySampleRate(31400), mySampleQueue(queuesize) { if(1) { if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { cerr << "WARNING: Couldn't initialize SDL audio system! " << endl; cerr << " " << SDL_GetError() << endl; myIsInitializedFlag = false; mySampleRate = 0; return; } SDL_AudioSpec desired; desired.freq = mySampleRate; desired.format = AUDIO_U8; desired.channels = 1; desired.samples = myFragmentSize; desired.callback = callback; desired.userdata = (void*)this; if(SDL_OpenAudio(&desired, &myHardwareSpec) < 0) { cerr << "WARNING: Couldn't open SDL audio system! " << endl; cerr << " " << SDL_GetError() << endl; myIsInitializedFlag = false; mySampleRate = 0; return; } // Make sure the sample buffer isn't to big (if it is the sound code // will not work so we'll need to disable the audio support) if(((float)myHardwareSpec.size / (float)myHardwareSpec.freq) >= 0.25) { cerr << "WARNING: Audio device doesn't support real time audio! Make "; cerr << "sure a sound" << endl; cerr << " server isn't running. Audio is disabled..." << endl; SDL_CloseAudio(); myIsInitializedFlag = false; mySampleRate = 0; return; } myIsInitializedFlag = true; myIsMuted = false; mySampleRate = myHardwareSpec.freq; myFragmentSize = myHardwareSpec.samples; // cerr << "Freq: " << (int)mySampleRate << endl; // cerr << "Format: " << (int)myHardwareSpec.format << endl; // cerr << "Channels: " << (int)myHardwareSpec.channels << endl; // cerr << "Silence: " << (int)myHardwareSpec.silence << endl; // cerr << "Samples: " << (int)myHardwareSpec.samples << endl; // cerr << "Size: " << (int)myHardwareSpec.size << endl; // Now initialize the TIASound object which will actually generate sound Tia_sound_init(31400, mySampleRate); // And start the SDL sound subsystem ... SDL_PauseAudio(0); } else { myIsInitializedFlag = false; myIsMuted = true; mySampleRate = 0; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL::~SoundSDL() { if(myIsInitializedFlag) { SDL_CloseAudio(); } myIsInitializedFlag = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SoundSDL::isSuccessfullyInitialized() const { return myIsInitializedFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::mute(bool state) { if(!myIsInitializedFlag) { return; } // Ignore multiple calls to do the same thing if(myIsMuted == state) { return; } myIsMuted = state; SDL_PauseAudio(myIsMuted ? 1 : 0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::setVolume(Int32 percent) { if(myIsInitializedFlag) { if((percent >= 0) && (percent <= 100)) { SDL_LockAudio(); myCurrentVolume = (uInt32)(((float)percent / 100.0) * SDL_MIX_MAXVOLUME); SDL_UnlockAudio(); } else if(percent == -1) // If -1 has been specified, play sound at default volume { myCurrentVolume = SDL_MIX_MAXVOLUME; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::update() { #if !defined(DIGITAL_SOUND) if(!myPauseStatus && myIsInitializedFlag) { // Make sure we have exclusive access to the sample queue // SDL_LockAudio(); // Generate enough samples to keep the sample queue full to capacity uInt32 numbytes = mySampleQueue.capacity() - mySampleQueue.size(); uInt8 buffer[numbytes]; Tia_process(buffer, numbytes); mySampleQueue.enqueue(buffer, numbytes); // Release lock on the sample queue // SDL_UnlockAudio(); } #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::set(uInt16 addr, uInt8 value) { #if defined(DIGITAL_SOUND) // Calculate the number of samples that need to be generated based on the // number of CPU cycles which have passed since the last sound update uInt32 samplesToGenerate = (mySampleRate * (mySystem->cycles() - myLastSoundUpdateCycle)) / 1190000; // Update counters and create samples if there's one sample to generate // TODO: This doesn't handle rounding quite right (10/08/2002) if(samplesToGenerate >= 1) { uInt8 buffer[1024]; for(Int32 sg = (Int32)samplesToGenerate; sg > 0; sg -= 1024) { Tia_process(buffer, ((sg >= 1024) ? 1024 : sg)); mySampleQueue.enqueue(buffer, ((sg >= 1024) ? 1024 : sg)); } myLastSoundUpdateCycle = myLastSoundUpdateCycle + ((samplesToGenerate * 1190000) / mySampleRate); } if(addr != 0) { Update_tia_sound(addr, value); } #else Update_tia_sound(addr, value); #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SoundSDL::save(Serializer& out) { string device = "TIASound"; try { out.putString(device); uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; // Only get the TIA sound registers if sound is enabled if(myIsInitializedFlag) Tia_get_registers(®1, ®2, ®3, ®4, ®5, ®6); out.putLong(reg1); out.putLong(reg2); out.putLong(reg3); out.putLong(reg4); out.putLong(reg5); out.putLong(reg6); out.putLong(myLastSoundUpdateCycle); } catch(char *msg) { cerr << msg << endl; return false; } catch(...) { cerr << "Unknown error in save state for " << device << endl; return false; } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SoundSDL::load(Deserializer& in) { string device = "TIASound"; try { if(in.getString() != device) return false; uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; reg1 = (uInt8) in.getLong(); reg2 = (uInt8) in.getLong(); reg3 = (uInt8) in.getLong(); reg4 = (uInt8) in.getLong(); reg5 = (uInt8) in.getLong(); reg6 = (uInt8) in.getLong(); myLastSoundUpdateCycle = (Int32) in.getLong(); // Only update the TIA sound registers if sound is enabled if(myIsInitializedFlag) Tia_set_registers(reg1, reg2, reg3, reg4, reg5, reg6); } catch(char *msg) { cerr << msg << endl; return false; } catch(...) { cerr << "Unknown error in load state for " << device << endl; return false; } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::callback(void* udata, uInt8* stream, int len) { SoundSDL* sound = (SoundSDL*)udata; if(!sound->isSuccessfullyInitialized()) { return; } if(sound->mySampleQueue.size() > 0) { Int32 offset; uInt8 buffer[2048]; for(offset = 0; (offset < len) && (sound->mySampleQueue.size() > 0); ) { uInt32 s = sound->mySampleQueue.dequeue(buffer, (2048 > (len - offset) ? (len - offset) : 2048)); SDL_MixAudio(stream + offset, buffer, s, sound->myCurrentVolume); offset += s; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL::SampleQueue::SampleQueue(uInt32 capacity) : myCapacity(capacity), myBuffer(0), mySize(0), myHead(0), myTail(0) { myBuffer = new uInt8[myCapacity]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL::SampleQueue::~SampleQueue() { delete[] myBuffer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::SampleQueue::clear() { myHead = myTail = mySize = 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 SoundSDL::SampleQueue::dequeue(uInt8* buffer, uInt32 size) { // We can only dequeue up to the number of items in the queue if(size > mySize) { size = mySize; } if((myHead + size) < myCapacity) { memcpy((void*)buffer, (const void*)(myBuffer + myHead), size); myHead += size; } else { uInt32 s1 = myCapacity - myHead; uInt32 s2 = size - s1; memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1); memcpy((void*)(buffer + s1), (const void*)myBuffer, s2); myHead = (myHead + size) % myCapacity; } mySize -= size; return size; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::SampleQueue::enqueue(uInt8* buffer, uInt32 size) { // If an attempt is made to enqueue more than the queue can hold then // we'll only enqueue the last myCapacity elements. if(size > myCapacity) { buffer += (size - myCapacity); size = myCapacity; } if((myTail + size) < myCapacity) { memcpy((void*)(myBuffer + myTail), (const void*)buffer, size); myTail += size; } else { uInt32 s1 = myCapacity - myTail; uInt32 s2 = size - s1; memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1); memcpy((void*)myBuffer, (const void*)(buffer + s1), s2); myTail = (myTail + size) % myCapacity; } if((mySize + size) > myCapacity) { myHead = (myHead + ((mySize + size) - myCapacity)) % myCapacity; mySize = myCapacity; } else { mySize += size; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 SoundSDL::SampleQueue::size() const { return mySize; }
/*****************************************************************************/ /* */ /* Module: TIA Chip Sound Simulator */ /* Purpose: To emulate the sound generation hardware of the Atari TIA chip. */ /* Author: Ron Fries */ /* */ /* Revision History: */ /* 10-Sep-96 - V1.0 - Initial Release */ /* 14-Jan-97 - V1.1 - Cleaned up sound output by eliminating counter */ /* reset. */ /* */ /*****************************************************************************/ /* */ /* License Information and Copyright Notice */ /* ======================================== */ /* */ /* TiaSound is Copyright(c) 1996 by Ron Fries */ /* */ /* This library is free software; you can redistribute it and/or modify it */ /* under the terms of version 2 of the GNU Library General Public License */ /* as published by the Free Software Foundation. */ /* */ /* This library is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */ /* General Public License for more details. */ /* To obtain a copy of the GNU Library General Public License, write to the */ /* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* */ /* Any permitted reproduction of these routines, in whole or in part, must */ /* bear this legend. */ /* */ /*****************************************************************************/ #ifdef __cplusplus extern "C" { #endif #include <stdio.h> #include <stdlib.h> #include <time.h> /* define some data types to keep it platform independent */ #ifdef WIN32 #define int8 char #define int16 short #define int32 int #else #define int8 char #define int16 int #define int32 long #endif #define uint8 unsigned int8 #define uint16 unsigned int16 #define uint32 unsigned int32 /* CONSTANT DEFINITIONS */ /* definitions for AUDCx (15, 16) */ #define SET_TO_1 0x00 /* 0000 */ #define POLY4 0x01 /* 0001 */ #define DIV31_POLY4 0x02 /* 0010 */ #define POLY5_POLY4 0x03 /* 0011 */ #define PURE 0x04 /* 0100 */ #define PURE2 0x05 /* 0101 */ #define DIV31_PURE 0x06 /* 0110 */ #define POLY5_2 0x07 /* 0111 */ #define POLY9 0x08 /* 1000 */ #define POLY5 0x09 /* 1001 */ #define DIV31_POLY5 0x0a /* 1010 */ #define POLY5_POLY5 0x0b /* 1011 */ #define DIV3_PURE 0x0c /* 1100 */ #define DIV3_PURE2 0x0d /* 1101 */ #define DIV93_PURE 0x0e /* 1110 */ #define DIV3_POLY5 0x0f /* 1111 */ #define DIV3_MASK 0x0c #define AUDC0 0x15 #define AUDC1 0x16 #define AUDF0 0x17 #define AUDF1 0x18 #define AUDV0 0x19 #define AUDV1 0x1a /* the size (in entries) of the 4 polynomial tables */ #define POLY4_SIZE 0x000f #define POLY5_SIZE 0x001f #define POLY9_SIZE 0x01ff /* channel definitions */ #define CHAN1 0 #define CHAN2 1 #define FALSE 0 #define TRUE 1 /* LOCAL GLOBAL VARIABLE DEFINITIONS */ /* structures to hold the 6 tia sound control bytes */ static uint8 AUDC[2]; /* AUDCx (15, 16) */ static uint8 AUDF[2]; /* AUDFx (17, 18) */ static uint8 AUDV[2]; /* AUDVx (19, 1A) */ static uint8 Outvol[2]; /* last output volume for each channel */ /* Initialze the bit patterns for the polynomials. */ /* The 4bit and 5bit patterns are the identical ones used in the tia chip. */ /* Though the patterns could be packed with 8 bits per byte, using only a */ /* single bit per byte keeps the math simple, which is important for */ /* efficient processing. */ static uint8 Bit4[POLY4_SIZE] = { 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 }; static uint8 Bit5[POLY5_SIZE] = { 0,0,1,0,1,1,0,0,1,1,1,1,1,0,0,0,1,1,0,1,1,1,0,1,0,1,0,0,0,0,1 }; /* I've treated the 'Div by 31' counter as another polynomial because of */ /* the way it operates. It does not have a 50% duty cycle, but instead */ /* has a 13:18 ratio (of course, 13+18 = 31). This could also be */ /* implemented by using counters. */ static uint8 Div31[POLY5_SIZE] = { 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 }; /* Rather than have a table with 511 entries, I use a random number */ /* generator. */ static uint8 Bit9[POLY9_SIZE]; static uint8 P4[2]; /* Position pointer for the 4-bit POLY array */ static uint8 P5[2]; /* Position pointer for the 5-bit POLY array */ static uint16 P9[2]; /* Position pointer for the 9-bit POLY array */ static uint8 Div_n_cnt[2]; /* Divide by n counter. one for each channel */ static uint8 Div_n_max[2]; /* Divide by n maximum, one for each channel */ /* In my routines, I treat the sample output as another divide by N counter. */ /* For better accuracy, the Samp_n_cnt has a fixed binary decimal point */ /* which has 8 binary digits to the right of the decimal point. */ static uint16 Samp_n_max; /* Sample max, multiplied by 256 */ static uint16 Samp_n_cnt; /* Sample cnt. */ /*****************************************************************************/ /* Module: Tia_sound_init() */ /* Purpose: to handle the power-up initialization functions */ /* these functions should only be executed on a cold-restart */ /* */ /* Author: Ron Fries */ /* Date: September 10, 1996 */ /* */ /* Inputs: sample_freq - the value for the '30 Khz' Tia audio clock */ /* playback_freq - the playback frequency in samples per second */ /* */ /* Outputs: Adjusts local globals - no return value */ /* */ /*****************************************************************************/ void Tia_sound_init (uint16 sample_freq, uint16 playback_freq) { uint8 chan; int16 n; /* fill the 9bit polynomial with random bits */ for (n=0; n<POLY9_SIZE; n++) { Bit9[n] = rand() & 0x01; /* fill poly9 with random bits */ } /* calculate the sample 'divide by N' value based on the playback freq. */ Samp_n_max = (uint16)(((uint32)sample_freq<<8)/playback_freq); Samp_n_cnt = 0; /* initialize all bits of the sample counter */ /* initialize the local globals */ for (chan = CHAN1; chan <= CHAN2; chan++) { Outvol[chan] = 0; Div_n_cnt[chan] = 0; Div_n_max[chan] = 0; AUDC[chan] = 0; AUDF[chan] = 0; AUDV[chan] = 0; P4[chan] = 0; P5[chan] = 0; P9[chan] = 0; } } /*****************************************************************************/ /* Module: Update_tia_sound() */ /* Purpose: To process the latest control values stored in the AUDF, AUDC, */ /* and AUDV registers. It pre-calculates as much information as */ /* possible for better performance. This routine has not been */ /* optimized. */ /* */ /* Author: Ron Fries */ /* Date: January 14, 1997 */ /* */ /* Inputs: addr - the address of the parameter to be changed */ /* val - the new value to be placed in the specified address */ /* */ /* Outputs: Adjusts local globals - no return value */ /* */ /*****************************************************************************/ void Update_tia_sound (uint16 addr, uint8 val) { uint16 new_val = 0; uint8 chan; /* determine which address was changed */ switch (addr) { case AUDC0: AUDC[0] = val & 0x0f; chan = 0; break; case AUDC1: AUDC[1] = val & 0x0f; chan = 1; break; case AUDF0: AUDF[0] = val & 0x1f; chan = 0; break; case AUDF1: AUDF[1] = val & 0x1f; chan = 1; break; case AUDV0: AUDV[0] = (val & 0x0f) << 3; chan = 0; break; case AUDV1: AUDV[1] = (val & 0x0f) << 3; chan = 1; break; default: chan = 255; break; } /* if the output value changed */ if (chan != 255) { /* an AUDC value of 0 is a special case */ if (AUDC[chan] == SET_TO_1) { /* indicate the clock is zero so no processing will occur */ new_val = 0; /* and set the output to the selected volume */ Outvol[chan] = AUDV[chan]; } else { /* otherwise calculate the 'divide by N' value */ new_val = AUDF[chan] + 1; /* if bits 2 & 3 are set, then multiply the 'div by n' count by 3 */ if ((AUDC[chan] & DIV3_MASK) == DIV3_MASK) { new_val *= 3; } } /* only reset those channels that have changed */ if (new_val != Div_n_max[chan]) { /* reset the divide by n counters */ Div_n_max[chan] = new_val; /* if the channel is now volume only or was volume only */ if ((Div_n_cnt[chan] == 0) || (new_val == 0)) { /* reset the counter (otherwise let it complete the previous) */ Div_n_cnt[chan] = new_val; } } } } /*****************************************************************************/ /* Module: Tia_process_2() */ /* Purpose: To fill the output buffer with the sound output based on the */ /* tia chip parameters. This routine has not been optimized. */ /* Though it is not used by the program, I've left it for reference.*/ /* */ /* Author: Ron Fries */ /* Date: September 10, 1996 */ /* */ /* Inputs: *buffer - pointer to the buffer where the audio output will */ /* be placed */ /* n - size of the playback buffer */ /* */ /* Outputs: the buffer will be filled with n bytes of audio - no return val */ /* */ /*****************************************************************************/ void Tia_process_2 (register unsigned char *buffer, register uint16 n) { register uint8 chan; /* loop until the buffer is filled */ while (n) { /* loop through the channels */ for (chan = CHAN1; chan <= CHAN2; chan++) { /* NOTE: this routine intentionally does not count down to zero */ /* since 0 is used as a special case - no clock */ /* if the divide by N counter can count down */ if (Div_n_cnt[chan] > 1) { /* decrement and loop */ Div_n_cnt[chan]--; } /* otherwise if we've reached the bottom */ else if (Div_n_cnt[chan] == 1) { /* reset the counter */ Div_n_cnt[chan] = Div_n_max[chan]; /* the P5 counter has multiple uses, so we inc it here */ P5[chan]++; if (P5[chan] == POLY5_SIZE) P5[chan] = 0; /* check clock modifier for clock tick */ /* if we're using pure tones OR we're using DIV31 and the DIV31 bit is set OR we're using POLY5 and the POLY5 bit is set */ if (((AUDC[chan] & 0x02) == 0) || (((AUDC[chan] & 0x01) == 0) && Div31[P5[chan]]) || (((AUDC[chan] & 0x01) == 1) && Bit5[P5[chan]])) { if (AUDC[chan] & 0x04) /* pure modified clock selected */ { if (Outvol[chan]) /* if the output was set */ Outvol[chan] = 0; /* turn it off */ else Outvol[chan] = AUDV[chan]; /* else turn it on */ } else if (AUDC[chan] & 0x08) /* check for p5/p9 */ { if (AUDC[chan] == POLY9) /* check for poly9 */ { /* inc the poly9 counter */ P9[chan]++; if (P9[chan] == POLY9_SIZE) P9[chan] = 0; if (Bit9[P9[chan]]) /* if poly9 bit is set */ Outvol[chan] = AUDV[chan]; else Outvol[chan] = 0; } else /* must be poly5 */ { if (Bit5[P5[chan]]) Outvol[chan] = AUDV[chan]; else Outvol[chan] = 0; } } else /* poly4 is the only remaining option */ { /* inc the poly4 counter */ P4[chan]++; if (P4[chan] == POLY4_SIZE) P4[chan] = 0; if (Bit4[P4[chan]]) Outvol[chan] = AUDV[chan]; else Outvol[chan] = 0; } } } } /* decrement the sample counter - value is 256 since the lower byte contains the fractional part */ Samp_n_cnt -= 256; /* if the count down has reached zero */ if (Samp_n_cnt < 256) { /* adjust the sample counter */ Samp_n_cnt += Samp_n_max; /* calculate the latest output value and place in buffer */ *(buffer++) = Outvol[0] + Outvol[1]; /* and indicate one less byte to process */ n--; } } } /*****************************************************************************/ /* Module: Tia_process() */ /* Purpose: To fill the output buffer with the sound output based on the */ /* tia chip parameters. This routine has been optimized. */ /* */ /* Author: Ron Fries */ /* Date: September 10, 1996 */ /* */ /* Inputs: *buffer - pointer to the buffer where the audio output will */ /* be placed */ /* n - size of the playback buffer */ /* */ /* Outputs: the buffer will be filled with n bytes of audio - no return val */ /* */ /*****************************************************************************/ void Tia_process (register unsigned char *buffer, register uint16 n) { register uint8 audc0,audv0,audc1,audv1; register uint8 div_n_cnt0,div_n_cnt1; register uint8 p5_0, p5_1,outvol_0,outvol_1; audc0 = AUDC[0]; audv0 = AUDV[0]; audc1 = AUDC[1]; audv1 = AUDV[1]; /* make temporary local copy */ p5_0 = P5[0]; p5_1 = P5[1]; outvol_0 = Outvol[0]; outvol_1 = Outvol[1]; div_n_cnt0 = Div_n_cnt[0]; div_n_cnt1 = Div_n_cnt[1]; /* loop until the buffer is filled */ while (n) { /* Process channel 0 */ if (div_n_cnt0 > 1) { div_n_cnt0--; } else if (div_n_cnt0 == 1) { div_n_cnt0 = Div_n_max[0]; /* the P5 counter has multiple uses, so we inc it here */ p5_0++; if (p5_0 == POLY5_SIZE) p5_0 = 0; /* check clock modifier for clock tick */ if (((audc0 & 0x02) == 0) || (((audc0 & 0x01) == 0) && Div31[p5_0]) || (((audc0 & 0x01) == 1) && Bit5[p5_0])) { if (audc0 & 0x04) /* pure modified clock selected */ { if (outvol_0) /* if the output was set */ outvol_0 = 0; /* turn it off */ else outvol_0 = audv0; /* else turn it on */ } else if (audc0 & 0x08) /* check for p5/p9 */ { if (audc0 == POLY9) /* check for poly9 */ { /* inc the poly9 counter */ P9[0]++; if (P9[0] == POLY9_SIZE) P9[0] = 0; if (Bit9[P9[0]]) outvol_0 = audv0; else outvol_0 = 0; } else /* must be poly5 */ { if (Bit5[p5_0]) outvol_0 = audv0; else outvol_0 = 0; } } else /* poly4 is the only remaining option */ { /* inc the poly4 counter */ P4[0]++; if (P4[0] == POLY4_SIZE) P4[0] = 0; if (Bit4[P4[0]]) outvol_0 = audv0; else outvol_0 = 0; } } } /* Process channel 1 */ if (div_n_cnt1 > 1) { div_n_cnt1--; } else if (div_n_cnt1 == 1) { div_n_cnt1 = Div_n_max[1]; /* the P5 counter has multiple uses, so we inc it here */ p5_1++; if (p5_1 == POLY5_SIZE) p5_1 = 0; /* check clock modifier for clock tick */ if (((audc1 & 0x02) == 0) || (((audc1 & 0x01) == 0) && Div31[p5_1]) || (((audc1 & 0x01) == 1) && Bit5[p5_1])) { if (audc1 & 0x04) /* pure modified clock selected */ { if (outvol_1) /* if the output was set */ outvol_1 = 0; /* turn it off */ else outvol_1 = audv1; /* else turn it on */ } else if (audc1 & 0x08) /* check for p5/p9 */ { if (audc1 == POLY9) /* check for poly9 */ { /* inc the poly9 counter */ P9[1]++; if (P9[1] == POLY9_SIZE) P9[1] = 0; if (Bit9[P9[1]]) outvol_1 = audv1; else outvol_1 = 0; } else /* must be poly5 */ { if (Bit5[p5_1]) outvol_1 = audv1; else outvol_1 = 0; } } else /* poly4 is the only remaining option */ { /* inc the poly4 counter */ P4[1]++; if (P4[1] == POLY4_SIZE) P4[1] = 0; if (Bit4[P4[1]]) outvol_1 = audv1; else outvol_1 = 0; } } } /* decrement the sample counter - value is 256 since the lower byte contains the fractional part */ Samp_n_cnt -= 256; /* if the count down has reached zero */ if (Samp_n_cnt < 256) { /* adjust the sample counter */ Samp_n_cnt += Samp_n_max; /* calculate the latest output value and place in buffer */ #ifdef MAC_OSX *(buffer++) = (outvol_0 + outvol_1)/2 + 128; #else *(buffer++) = outvol_0 + outvol_1; #endif /* and indicate one less byte to process */ n--; } } /* save for next round */ P5[0] = p5_0; P5[1] = p5_1; Outvol[0] = outvol_0; Outvol[1] = outvol_1; Div_n_cnt[0] = div_n_cnt0; Div_n_cnt[1] = div_n_cnt1; } /*****************************************************************************/ /* Module: Tia_get_registers() */ /* Purpose: Returns the 6 TIA sound registers for use in state */ /* loading and saving. */ /* */ /* Author: Stephen Anthony */ /* Date: October 31, 2002 */ /* */ /* Inputs: reg .. reg6 - pointers to the variables where the registers */ /* will be placed */ /* */ /*****************************************************************************/ void Tia_get_registers (unsigned char *reg1, unsigned char *reg2, unsigned char *reg3, unsigned char *reg4, unsigned char *reg5, unsigned char *reg6) { *reg1 = AUDC[0]; *reg2 = AUDC[1]; *reg3 = AUDF[0]; *reg4 = AUDF[1]; *reg5 = AUDV[0]; *reg6 = AUDV[1]; } /*****************************************************************************/ /* Module: Tia_set_registers() */ /* Purpose: Sets the 6 TIA sound registers for use in state */ /* loading and saving. */ /* */ /* Author: Stephen Anthony */ /* Date: October 31, 2002 */ /* */ /* Inputs: reg .. reg6 - the registers to be set */ /* */ /*****************************************************************************/ void Tia_set_registers (unsigned char reg1, unsigned char reg2, unsigned char reg3, unsigned char reg4, unsigned char reg5, unsigned char reg6) { AUDC[0] = reg1; AUDC[1] = reg2; AUDF[0] = reg3; AUDF[1] = reg4; AUDV[0] = reg5; AUDV[1] = reg6; } #ifdef __cplusplus } #endif
;* ;* z26 sound stuff ;* ;* it's up to the *operating system* to empty the sound queue ;* it's up to the z26 core to fill it up ;* ; ; z26 is Copyright 1997-2003 by John Saeger and is a derived work with many ; contributors. z26 is released subject to the terms and conditions of the ; GNU General Public License Version 2 (GPL). z26 comes with no warranty. ; Please see COPYING.TXT for details. [section .data] ;; ;_SQ_Max dd 3072*3 ;; SQ_Start: ; byte ; <-- start clearing here ;; ;; SQ_Input dd 0 ; point to next avail byte for storing ;; SQ_Output dd 0 ; point to next avail byte for fetching ;; SQ_Count dd 0 ;; SQ_Top dd 0 ;; ;; ;SQ_MAX = 3072*3 ; 3072, 1024 ;; ;; ;_SoundQ times 65000 db 0 ;; SQ_End: ; byte ; <-- finish clearing here [section .code] ;* ;* Initialize sound queue ;* ;; Init_SoundQ: ;; clear_mem SQ_Start, SQ_End ;; ;; mov dword [SQ_Input], SoundQ ; initialize sound queue stuff ;; mov dword [SQ_Output], SoundQ ;; mov dword [SQ_Count],0 ;; mov eax, SoundQ ;; inc eax ;; add eax,dword [SQ_Max] ;; mov dword [SQ_Top],eax ;; ; mov [SQ_Top], SoundQ + SQ_MAX + 1 ;; ;; ret ;* ;* routine to get status of sound queue ;* ;* returns: ;* ;* -1 if sound queue full and no room for more output ;* 0 if there's too much room (less than 1/2 full) ;* 1 if there's just enough room (more than 1/2 full) ;* SQ_Test: cmp byte [quiet],0 ; doing sound at all? jnz near SQ_Just_Right ; no, pretend queue is *just right* mov eax,dword [SQ_Max] cmp dword [SQ_Count],eax ; sound queue already full? jae near SQ_Full ; yes, don't queue anything shr eax,1 cmp dword [SQ_Count],eax ; less than 1/2 full? jbe near SQ_Empty ; yes SQ_Just_Right: mov eax,1 ; no ret SQ_Empty: mov eax,0 ret SQ_Full: mov eax,-1 ret ;* ;* routine to put byte in al in the sound queue ;* always make sure SQ_Count < SQ_MAX before calling ;* ;;; extern debug_sound_byte ;;;DEBUG SQ_Store: ;;; pushad ;;; DEBUG ;;; push eax ;;; DEBUG ;;; call debug_sound_byte ;;; DEBUG ;;; add esp, 4 ;;; popad ;;;DEBUG ;;; what goes into the queue is what comes out - error in tiasnd, not here cmp byte [quiet],0 ; doing sound at all? jnz near SQS_skip_store ; no, don't store sound byte ; pushad ; call srv_lock_audio ; popad mov esi,dword [SQ_Input] mov byte [esi],al inc esi inc dword [SQ_Count] cmp esi,dword [SQ_Top] jb near SQS_done mov esi, SoundQ SQS_done: mov dword [SQ_Input],esi ; pushad ; call srv_unlock_audio ; popad SQS_skip_store: ret ;* ;* routine to put the sound in the sound buffer ;* global Tia_process Tia_process: pushad mov eax,dword [bufsize] cmp dword [SQ_Count],eax ; enough sound available? ja near Sound_Enough ; yes pushad ; no, make sure there's enough call QueueSoundBytes popad Sound_Enough: mov ecx,dword [bufsize] ; # of bytes mov edi,dword [DMABuf] mov esi,dword [SQ_Output] SoundFillLoop: mov al,byte [esi] inc esi cmp esi,dword [SQ_Top] jb near SF_done mov esi, SoundQ SF_done: mov byte [edi],al ; put it in soundblaster buffer inc edi dec ecx ; more room in soundblaster buffer? jnz near SoundFillLoop ; yes, get more mov eax,dword [bufsize] sub dword [SQ_Count],eax mov dword [SQ_Output],esi SoundBufferBail: popad ret ;* ;* put a byte in the sound queue ;* QueueSoundByte: call SQ_Test cmp eax,-1 ; sound queue already full? jne near QSB_room ; no, there's room ; cmp [NoRetrace],-1 ; synchronizing to monitor? cmp byte [SyncToSoundBuffer],1 ; syncronizing to sound buffer? jne near SoundQueueFull ; no, don't queue anything pushad ; no, wait for room call srv_Events popad jmp QueueSoundByte QSB_room: call KidVid_Sound_Byte ; no, get kidvid sample push eax call TIA_Sound_Byte ; get TIA sample pop ebx ; kidvid sample add eax,ebx ; mix the samples shr eax,1 ;;; 20040416 bkw: DIRTY HACK ALERT! ;;; Something's wrong with the TIA code, causing the sound output to ;;; be too quiet for humans to hear (at least on Linux). Blindly ;;; adding 0x70 to the sound byte makes it audible (though probably ;;; wildly inaccurate). For one thing, we only ever get 3 values out ;;; of TIA_Sound_Byte (that's 0x00, 0x10, and 0x20, with 0x00 occurring ;;; only at the beginning of emulation!). That means we're effectively ;;; working at 1.5 bits of sample resolution! ;;; Until I can fix whatever's wrong with TIA_Sound_Byte, this will ;;; at least let people hear something. Note that I have NO IDEA ;;; what this does to the Windows version. ;;; add eax, 70h call SQ_Store ; put it in the sound queue SoundQueueFull: ret ;* ;* put sound bytes into buffer ;* called once per scan line ;* QueueSoundBytes: ; proc near SoundQueueLoop: call QueueSoundByte call QueueSoundByte call SQ_Test cmp eax,0 ; sound queue at least 1/2 full? je near SoundQueueLoop ; no ret ; QueueSoundBytes endp
; tiasnd.asm -- z26 sound generation routines ; based on TIASound (c) 1996-1997 by Ron Fries ; please see the end of this file for Ron's banner ; z26 is Copyright 1997-2003 by John Saeger and is a derived work with many ; contributors. z26 is released subject to the terms and conditions of the ; GNU General Public License Version 2 (GPL). z26 comes with no warranty. ; Please see COPYING.TXT for details. ; 04-08-98 -- first release ; 04-19-99 -- added sound queue ; 07-27-02 -- 32-bit ; 12-08-03 -- polynomials adapted to Adam Wozniak's description [section .data] sreg dd 1 ; initialize shift register to non-zero val ; Initialze the bit patterns for the polynomials. ; The 4bit and 5bit patterns are the identical ones used in the tia chip. ; Though the patterns could be packed with 8 bits per byte, using only a ; single bit per byte keeps the math simple, which is important for ; efficient processing. ;Bit4 db 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 ;Bit5 db 0,0,1,0,1,1,0,0,1,1,1,1,1,0,0,0,1,1,0,1,1,1,0,1,0,1,0,0,0,0,1 Bit4 db 0,1,1,0,0,1,0,1,0,0,0,0,1,1,1 Bit5 db 0,0,0,0,0,1,1,1,0,0,1,0,0,0,1,0,1,0,1,1,1,1,0,1,1,0,1,0,0,1,1 ; 1 = toggle output in 5 bit poly - used when poly5 clocks other outputs Bit5T db 1,0,0,0,0,1,0,0,1,0,1,1,0,0,1,1,1,1,1,0,0,0,1,1,0,1,1,1,0,1,0 ; The 'Div by 31' counter is treated as another polynomial because of ; the way it operates. It does not have a 50% duty cycle, but instead ; has a 13:18 ratio (of course, 13+18 = 31). This could also be ; implemented by using counters. Div31 db 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 Div6 db 0,1,0,0,1,0 ; The sample output is treated as another divide by N counter. ; For better accuracy, the Samp_n_cnt has a fixed binary decimal point ; which has 8 binary digits to the right of the decimal point. Samp_n_cnt dd 0 Samp_n_max dd 0 TS_Start: ; byte ; <-- start clearing here D6 db 0,0 P4 db 0,0 P5 db 0,0 AUDC db 0,0 AUDF db 0,0 AUDV db 0,0 Outvol db 0,0 Div_n_cnt db 0,0 Div_n_max db 0,0 TS_End: ; byte ; <-- finish clearing here P9_sreg dd 1,1 new_val dd 0 prev_sample dd 0 next_sample db 0 AUDC_Jumptab: ; dword ; HEX D3 D2 D1 D0 Clock Source Clock Modifier Source Pattern ; --- ------------- -------------- ---------------- ---------------- dd TSB_Ch0done ; 0 0 0 0 0 3.58 MHz/114 -> none (pure) -> none dd TSB_Poly4 ; 1 0 0 0 1 3.58 MHz/114 -> none (pure) -> 4-bit poly dd TSB_Div31_Poly4 ; 2 0 0 1 0 3.58 MHz/114 -> divide by 31 -> 4-bit poly dd TSB_Poly5_Poly4 ; 3 0 0 1 1 3.58 MHz/114 -> 5-bit poly -> 4-bit poly dd TSB_Pure ; 4 0 1 0 0 3.58 MHz/114 -> none (pure) -> pure (~Q) dd TSB_Pure ; 5 0 1 0 1 3.58 MHz/114 -> none (pure) -> pure (~Q) dd TSB_Div31_Pure ; 6 0 1 1 0 3.58 MHz/114 -> divide by 31 -> pure (~Q) ; dd TSB_Poly5_Pure ; 7 0 1 1 1 3.58 MHz/114 -> 5-bit poly -> pure (~Q) dd TSB_Poly5 ; 7 0 1 1 1 3.58 MHz/114 -> none (pure) -> 5-bit poly dd TSB_Poly9 ; 8 1 0 0 0 3.58 MHz/114 -> none (pure) -> 9-bit poly dd TSB_Poly5 ; 9 1 0 0 1 3.58 MHz/114 -> none (pure) -> 5-bit poly ; dd TSB_Div31_Poly5 ; A 1 0 1 0 3.58 MHz/114 -> divide by 31 -> 5-bit poly dd TSB_Div31_Pure ; A 1 0 1 0 3.58 MHz/114 -> divide by 31 -> pure (~Q) dd TSB_Poly5_Poly5 ; B 1 0 1 1 3.58 MHz/114 -> 5-bit poly -> 5-bit poly dd TSB_Div6_Pure ; C 1 1 0 0 3.58 MHz/114 -> divide by 6 -> pure (~Q) dd TSB_Div6_Pure ; D 1 1 0 1 3.58 MHz/114 -> divide by 6 -> pure (~Q) dd TSB_Div31_Div6 ; E 1 1 1 0 3.58 MHz/114 -> divide by 31 -> divide by 6 dd TSB_Poly5_Div6 ; F 1 1 1 1 3.58 MHz/114 -> 5-bit poly -> divide by 6 [section .code] ;* ;* handle the power-up initialization functions ;* these functions should only be executed on a cold-restart ;* Init_Tiasnd: ; calculate the sample 'divide by N' value based on the playback freq mov eax,31400 shl eax,8 mov ebx,31400 xor edx,edx div ebx ; ax = (_sample_freq<<8)/_playback_freq mov dword [Samp_n_max],eax mov dword [Samp_n_cnt],0 clear_mem TS_Start, TS_End mov dword [P9_sreg],1 mov dword [P9_sreg+4],1 ret ;* ;* routine to get kid-vid sound byte ;* KidVid_Sound_Byte: test byte [KidVid],0ffH jz near NoSamplePlay pushad call kv_GetNextSampleByte popad movzx eax,byte [SampleByte] NoSamplePlay: ret ;* ;* generate a sequence of pseudo-random bits 511 bits long ;* by emulating a 9-bit shift register with feedback taps at ;* positions 5 and 9. ;* ShiftRegister9: mov eax,dword [sreg] mov edx,eax and eax,1 ; bit 9 (register output & return val) and edx,16 shr edx,4 ; position bit 5 at bottom xor edx,eax ; xor with bit 9 = new bit 1 shr dword [sreg],1 ; shift the register shl edx,8 ; position the feedback bit or dword [sreg],edx ; or it in ret ;* ;* update TIA sound registers ;* H_AUDC0: mov al,byte [WByte] and al,15 mov byte [AUDC],al jmp UTS_Chan0 H_AUDC1: mov al,byte [WByte] and al,15 mov byte [AUDC+1],al jmp UTS_Chan1 H_AUDF0: mov al,byte [WByte] and al,31 mov byte [AUDF],al jmp UTS_Chan0 H_AUDF1: mov al,byte [WByte] and al,31 mov byte [AUDF+1],al jmp UTS_Chan1 H_AUDV0: mov al,byte [WByte] and al,15 shl al,3 mov byte [AUDV],al UTS_Chan0: xor ebx,ebx jmp UTS_RegSet H_AUDV1: mov al,byte [WByte] and al,15 shl al,3 mov byte [AUDV+1],al UTS_Chan1: mov ebx,1 ; the output value has changed UTS_RegSet: cmp byte [AUDC+ebx],0 ; AUDC value of zero is a special case jne near UTS_rs1 mov dword [new_val],0 ; indicate clock is zero so ... mov al,byte [AUDV+ebx] ; ... no processing will occur mov byte [Outvol+ebx],al ; and set output to selected volume jmp UTS_rs2 UTS_rs1: movzx eax,byte [AUDF+ebx] ; calc the 'div by N' value inc eax mov dword [new_val],eax ; mov al,[AUDC+ebx] ; and al,12 ; cmp al,12 ; jne near UTS_rs2 ; if bits 2 and 3 are set ... ; mov eax,[_new_val] ; ... multiply by three ; add eax,eax ; add [_new_val],eax UTS_rs2: movzx eax,byte [Div_n_max+ebx] ; only reset channels that have changed cmp eax,dword [new_val] je near UTS_Done mov al,byte [new_val] mov byte [Div_n_max+ebx],al ; reset 'div by N' counters cmp byte [Div_n_cnt+ebx],0 je near UTS_rs3 ; if channel is now volume only ... cmp dword [new_val],0 jne near UTS_Done ; ... or was volume only ... UTS_rs3: mov al,byte [new_val] mov byte [Div_n_cnt+ebx],al ; ... reset the counter ; (otherwise complete previous) UTS_Done: ret ;* ;* generate a sound byte based on the TIA chip parameters ;* ;;; converted macro: %macro inc_mod 2 ; local ; done inc byte [%1] ;;; GUESSED dword cmp byte [%1],%2 ;;; GUESSED dword jne near %%done mov byte [%1],0 ;;; GUESSED dword %%done: %endmacro TIA_Sound_Byte: TSB_ProcessLoop: xor edi,edi ; process channel 0 first cmp byte [Pitfall2],0 ; doing Pitfall2? jz near TSB_ProcessChannel ; no inc edi ; yes, only do channel 1 TSB_ProcessChannel: cmp byte [Div_n_cnt + edi],1 ; if div by N counter can count down ... jb near TSB_Ch0done ; zero is special case, means AUDC==0 -- fast exit je near TSB_1 dec byte [Div_n_cnt + edi] ; ... decrement ... jmp TSB_Ch0done ; ... and do next channel TSB_1: mov al,byte [Div_n_max + edi] ; otherwise reset the counter and process this channel mov byte [Div_n_cnt + edi],al movzx esi,byte [AUDC + edi] ; AUDC = index into branch table inc_mod P5+edi,31 ; P5 channel has multiple uses (Div31 & P5), inc it here movzx ebx,byte [P5 + edi] jmp dword [AUDC_Jumptab + esi*4] ; process sound changes based on AUDC TSB_Div6_Pure: inc_mod D6+edi,6 ; inc Div6 counter movzx ebx,byte [D6 + edi] cmp byte [Div6+ebx],0 ; if div 6 bit set ... jnz near TSB_Pure ; ... do pure jmp TSB_Ch0done TSB_Div31_Div6: cmp byte [Div31+ebx],0 ; if div 31 bit set ... jnz near TSB_Div6_Pure ; ... do div 6 jmp TSB_Ch0done TSB_Div31_Pure: cmp byte [Div31+ebx],0 ; if div 31 bit set ... jnz near TSB_Pure ; ... do pure jmp TSB_Ch0done ;TSB_Poly5_Pure: ; cmp [Bit5+ebx],0 ; if div 5 bit set ... ; jz near TSB_Ch0done ; ... do pure TSB_Pure: cmp byte [Outvol + edi],0 ; toggle the output je near TSB_VolumeOn jmp TSB_VolumeOff TSB_Poly9: mov edx,dword [P9_sreg+4*edi] mov dword [sreg],edx ; set shift reg to this channel's shift reg call ShiftRegister9 ; calculate next bit mov edx,dword [sreg] ; save shift reg to our channel mov dword [P9_sreg+4*edi],edx test al,1 ; shift reg bit on? je near TSB_VolumeOff ; no jmp TSB_VolumeOn ; yes ;TSB_Div31_Poly5: ; cmp [Div31+ebx],0 ; if div 31 bit set ... ; jnz near TSB_Poly5 ; ... do Poly5 ; jmp TSB_Ch0done TSB_Poly5_Div6: cmp byte [Bit5T+ebx],0 ; if Bit5 output changed ... jnz near TSB_Div6_Pure ; ... do Div 6 jmp TSB_Ch0done TSB_Poly5_Poly5: cmp byte [Bit5+ebx],0 ; if Poly5 bit set ... jz near TSB_Ch0done ; ... do Poly5 TSB_Poly5: movzx ebx,byte [P5 + edi] ; set the output bit cmp byte [Bit5+ebx],0 je near TSB_VolumeOff jmp TSB_VolumeOn TSB_Div31_Poly4: cmp byte [Div31+ebx],0 ; if div 31 bit set ... jnz near TSB_Poly4 ; ... do Poly4 jmp TSB_Ch0done TSB_Poly5_Poly4: ; changed from Bit5 to Bit5T *EST* cmp byte [Bit5T+ebx],0 ; if Poly5 bit set ... jz near TSB_Ch0done ; ... do Poly4 TSB_Poly4: inc_mod P4+edi,15 ; inc P4 counter movzx ebx,byte [P4 + edi] cmp byte [Bit4+ebx],0 ; and set the output bit je near TSB_VolumeOff TSB_VolumeOn: mov al,byte [AUDV + edi] mov byte [Outvol + edi],al jmp TSB_Ch0done TSB_VolumeOff: mov byte [Outvol + edi],0 TSB_Ch0done: inc edi ; to next channel cmp edi,1 ; done ? jbe near TSB_ProcessChannel ; not yet sub dword [Samp_n_cnt],256 ; decrement sample count ; (256 since lower byte is ; fractional part) cmp dword [Samp_n_cnt],256 ; if count has reached zero ... jae near TSB_ProcessLoop mov eax,dword [Samp_n_max] ; ... adjust the sample counter add dword [Samp_n_cnt],eax cmp byte [Pitfall2],0 ; running Pitfall 2? jz near TSB_NotPitfall2 ; no call Clock_Pitfall2 ; yes, clock P2 music clock (and build AUDV) mov al,byte [Outvol+1] ; channel 1 mov ah,byte [P2_AUDV] and ah,15 shl ah,3 add al,ah ; add in Pitfall 2 AUDV byte jmp TSB_Pitfall2_Done TSB_NotPitfall2: mov al,byte [Outvol+0] ; not Pitfall 2, do normal mixing add al,byte [Outvol+1] ; sum the channels TSB_Pitfall2_Done: cmp byte [GamePaused],0 ; if game paused jz near TSB_NoSilence mov al,080h ; fill buffer with silence TSB_NoSilence: test byte [dsp],0ffh ; doing digital signal processing ? jz near TSB_ProcessDone ; no, just store it mov byte [next_sample],al ; yes, take edge off square wave xor eax,eax mov al,byte [next_sample] add eax,dword [prev_sample] shr eax,1 mov dword [prev_sample],eax ; dsp=2, scaled moving average cmp byte [dsp],1 jne near TSB_ProcessDone movzx esi,byte [next_sample] ; dsp=1, simple moving average mov dword [prev_sample],esi TSB_ProcessDone: and eax,0ffh ; return 32-bit sample ret ; /*****************************************************************************/ ; /* */ ; /* License Information and Copyright Notice */ ; /* ======================================== */ ; /* */ ; /* TiaSound is Copyright(c) 1996 by Ron Fries */ ; /* */ ; /* This library is free software; you can redistribute it and/or modify it */ ; /* under the terms of version 2 of the GNU Library General Public License */ ; /* as published by the Free Software Foundation. */ ; /* */ ; /* This library is distributed in the hope that it will be useful, but */ ; /* WITHOUT ANY WARRANTY; without even the implied warranty of */ ; /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */ ; /* General Public License for more details. */ ; /* To obtain a copy of the GNU Library General Public License, write to the */ ; /* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ ; /* */ ; /* Any permitted reproduction of these routines, in whole or in part, must */ ; /* bear this legend. */ ; /* */ ; /*****************************************************************************/
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: Aw: Re: [stella] The future of , Thomas Jentzsch | Thread | Re: Aw: Re: [stella] The future of , Andrew Davie |
Re: The Dig Found! (maybe?) Re: [st, Rob | Date | Re: Aw: Re: [stella] The future of , Andrew Davie |
Month |