- [NEW] [tia] AUDC0, AUDC1, AUDF0, AUDF1, AUDV0, AUDV1, GRP0, GRP1, ENAM0, ENAM1, ENABL

- [NEW] [tia] áudio en el seu propi mòdul, pa no tornar-se loco
- [NEW] Afegides frequències de rellotge per a NTSC, PAL, SECAM i PAL-M
- [NEW] Mòdul Speaker conectat a tia-audio
This commit is contained in:
Raimon @ quifisraimon
2026-09-16 10:26:42 +02:00
parent 4678620dae
commit 61d8b0a808
7 changed files with 342 additions and 11 deletions
+8 -2
View File
@@ -8,6 +8,11 @@
//#include "keyboard.h"
//#include "z80debugger.h"
#define NTSC_CLOCK 1193182.0f
#define PAL_CLOCK 1182298.0f
#define SECAM_CLOCK 1187500.0f
#define PAL_M_CLOCK 1191870.0f
namespace atari2600
{
static bool should_exit = false;
@@ -22,8 +27,9 @@ namespace atari2600
pia::reset();
display::init();
speaker::init();
//speaker::register_source(psg::get_sample);
speaker::init(NTSC_CLOCK);
speaker::register_source(tia::audio::get_sample0);
speaker::register_source(tia::audio::get_sample1);
//speaker::register_source(keyboard::get_sample);
}
+88
View File
@@ -0,0 +1,88 @@
#include "speaker.h"
#include <SDL3/SDL.h>
#include <vector>
#include <stdlib.h>
namespace speaker
{
int sampling_freq = 44100;
uint16_t audio_buffer_size = 1024;
SDL_AudioStream* sdlAudioStream = nullptr;
uint8_t *sound_buffer = nullptr;
uint16_t sound_pos=0;
float t_sound=0.0f;
float cycles_per_sample = 0.0f;
std::vector<uint8_t(*)()> sources;
void init(float clock_frequency)
{
if (sound_buffer) quit();
SDL_AudioSpec audioSpec{SDL_AUDIO_U8, 1, sampling_freq};
sdlAudioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audioSpec, nullptr, nullptr);
cycles_per_sample = clock_frequency / (float)sampling_freq;
sound_buffer = (uint8_t*)malloc(audio_buffer_size);
sound_pos = 0;
t_sound = 0.0f;
enable();
}
void quit()
{
disable();
sources.clear();
if (sound_buffer) {
free(sound_buffer);
sound_buffer = nullptr;
}
if (sdlAudioStream) {
SDL_DestroyAudioStream(sdlAudioStream);
sdlAudioStream = nullptr;
}
}
void enable()
{
SDL_ResumeAudioStreamDevice(sdlAudioStream);
}
void disable()
{
SDL_PauseAudioStreamDevice(sdlAudioStream);
}
void register_source(uint8_t(*callback)())
{
sources.push_back(callback);
}
void update(const uint32_t dt)
{
t_sound += (float)dt;
if (t_sound >= cycles_per_sample)
{
t_sound -= cycles_per_sample;
uint32_t sample = 0;
for (auto callback : sources) sample += callback();
sample /= sources.size();
sound_buffer[sound_pos++] = (uint8_t)sample;
}
if (sound_pos >= audio_buffer_size)
{
SDL_PutAudioStreamData(sdlAudioStream, sound_buffer, sound_pos);
sound_pos = 0;
while (SDL_GetAudioStreamQueued(sdlAudioStream) > 2048)
{
SDL_Delay(1);
}
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
namespace speaker
{
void init();
void init(float clock_frequency);
void quit();
void enable();
void disable();
+188
View File
@@ -0,0 +1,188 @@
#include "tia-audio.h"
namespace tia
{
namespace audio
{
static const uint16_t BIT_PATTERN_15 = 0x7556;
static const uint32_t BIT_PATTERN_31 = 0x74567254;
static const uint8_t LFSR511_TABLE[511] = {
1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,1,0,0,1,0,1,
0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,1,1,0,0,0,0,0,1,0,1,0,1,0,0,
1,0,0,1,0,0,0,1,0,0,1,1,0,0,0,1,0,1,1,1,0,0,1,1,1,0,1,0,1,1,0,
1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,0,1,0,0,1,0,1,1,1,1,1,0,0,0,1,0,
0,0,1,1,0,1,1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,0,
1,0,0,0,0,1,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,0,1,
1,1,0,1,0,0,0,1,0,1,0,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,
1,0,0,1,1,1,1,1,1,0,1,1,0,1,1,1,0,0,1,0,0,0,0,1,1,1,0,1,1,0,1,
1,1,0,0,1,0,1,1,0,1,1,0,0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,1,0,0,0,
1,0,1,1,1,1,0,0,1,0,1,0,0,0,1,0,1,1,0,1,0,1,1,1,0,1,1,0,0,0,0,
1,0,1,1,0,0,1,0,1,0,1,1,1,1,0,1,1,0,1,0,0,1,0,1,0,0,1,0,1,1,0,
0,0,0,0,1,0,0,0,1,1,1,1,1,1,0,1,0,0,0,0,0,1,1,1,0,0,0,1,0,1,0,
1,1,0,1,1,0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,0,0,0,0,0,1,1,0,1,1,1,
0,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,0,1,1,1,1,0,1,1,1,1,0,0,1,1,1,
1,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,1,0,1,1,0,1,0,0,1,0,0,0,1,1,0,
1,0,1,1,0,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,0,0,1,1,0,1,1,1,0,1,0,
1,0,0,0,0,0,1,1
};
uint8_t get_lfsr511_bit(uint32_t step)
{
return LFSR511_TABLE[step % 511];
}
uint8_t get_poly_mode_bit(uint8_t audc, uint32_t step)
{
switch (audc & 0x0F) {
case 0:
case 11:
return 1; // Direct volume (PCM output)
case 1:
// 4-bit Poly (15 steps)
return (BIT_PATTERN_15 >> (step % 15)) & 1;
case 2:
// 5-bit Poly -> 4-bit Poly divisor (31 steps)
return (BIT_PATTERN_31 >> (step % 31)) & 1;
case 3: {
// 5-bit Poly XOR 4-bit Poly (465 steps)
uint8_t b5 = (BIT_PATTERN_31 >> (step % 31)) & 1;
uint8_t b4 = (BIT_PATTERN_15 >> (step % 15)) & 1;
return b5 ^ b4;
}
case 4:
case 5:
// Pure Tone: Divide frequency clock by 2
return (step & 1);
case 6:
case 10:
// Divide by 31 then divide by 2
return ((step / 31) & 1);
case 7:
case 9:
// 5-bit Poly output variant
return ((BIT_PATTERN_31 >> ((step / 2) % 31)) & 1);
case 8:
// White Noise (9-bit Poly / 511 steps)
return get_lfsr511_bit(step);
case 12:
case 13:
// Pure Tone: Divide frequency clock by 6
return ((step / 3) & 1);
case 14:
// Divide by 93 (31 * 3) then divide by 2
return ((step / 93) & 1);
case 15:
// 5-bit Poly clocked down by 6
return ((BIT_PATTERN_31 >> ((step / 6) % 31)) & 1);
default:
return 0;
}
}
struct channel
{
uint8_t control = 0;
uint8_t frequency = 0;
uint8_t volume = 0;
uint8_t freq_counter = 0;
uint32_t poly_counter = 0;
};
channel channels[2];
void tick()
{
for (int i=0; i<2; ++i) {
if (channels[i].freq_counter == 0) {
channels[i].freq_counter = channels[i].frequency + 1;
channels[i].poly_counter++;
} else {
channels[i].freq_counter--;
}
}
}
void write(uint16_t address, uint8_t value)
{
switch (address) {
case 0x15: // AUDC0
channels[0].control = value & 0x0f; break;
case 0x16: // AUDC1
channels[1].control = value & 0x0f; break;
case 0x17: // AUDF0
channels[0].frequency = value & 0x1f; break;
case 0x18: // AUDF1
channels[1].frequency = value & 0x1f; break;
case 0x19: // AUDV0
channels[0].volume = value & 0x0f; break;
case 0x1A: // AUDV1
channels[1].volume = value & 0x0f; break;
}
}
uint8_t get_sample0() { return get_sample(0); }
uint8_t get_sample1() { return get_sample(1); }
uint8_t get_sample(uint8_t channel)
{
if (channels[channel].volume == 0) return 0;
bool bit_out = false;
switch (channels[channel].control) {
case 0:
case 11:
// Set to 1: Direct volume output (PCM mode)
bit_out = true;
break;
case 1:
// 4-bit Poly (15-step repeating sequence)
bit_out = (BIT_PATTERN_15 >> (channels[channel].poly_counter % 15)) & 1;
break;
case 2:
// 5-bit Poly (31-step repeating sequence)
bit_out = (BIT_PATTERN_31 >> (channels[channel].poly_counter % 31)) & 1;
break;
case 4:
case 5:
// Pure tone (Divide by 2 square wave)
bit_out = (channels[channel].poly_counter & 1);
break;
case 8:
// White noise (9-bit Poly / 511-step sequence)
bit_out = get_lfsr511_bit(channels[channel].poly_counter % 511);
break;
case 12:
case 13:
// Pure tone (Divide by 6 square wave)
bit_out = ((channels[channel].poly_counter / 3) & 1);
break;
default:
// Handle remaining poly modes (3, 6, 7, 9, 10, 14, 15) via lookup table
bit_out = get_poly_mode_bit(channels[channel].control, channels[channel].poly_counter);
break;
}
return bit_out ? channels[channel].volume * 17 : 0;
}
}
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
namespace tia
{
namespace audio
{
void tick();
void write(uint16_t address, uint8_t value);
uint8_t get_sample(uint8_t channel);
}
}
+37 -7
View File
@@ -1,4 +1,5 @@
#include "tia.h"
#include "tia-audio.h"
namespace tia
{
@@ -47,18 +48,13 @@ namespace tia
bool joy0_button_pressed = false;
bool joy1_button_pressed = false;
uint8_t missile0_size = 1;
uint8_t missile0_pos = 0;
uint8_t player0_size = 1;
uint8_t player0_num_copies = 1;
uint8_t player0_copies_distance = 0;
uint32_t player0_color = 0x00000000;
bool player0_reflect = false;
uint8_t player0_pos = 0;
uint8_t missile1_size = 1;
uint8_t missile1_pos = 0;
uint8_t player0_bits = 0;
uint8_t player1_size = 1;
uint8_t player1_num_copies = 1;
@@ -66,6 +62,15 @@ namespace tia
uint32_t player1_color = 0x00000000;
bool player1_reflect = false;
uint8_t player1_pos = 0;
uint8_t player1_bits = 0;
bool missile0_enabled = false;
uint8_t missile0_size = 1;
uint8_t missile0_pos = 0;
bool missile1_enabled = false;
uint8_t missile1_size = 1;
uint8_t missile1_pos = 0;
uint32_t playfield_color = 0x00000000;
bool playfield_reflected = false;
@@ -79,6 +84,7 @@ namespace tia
{16,17,18,19, 15,14,13,12,11,10,9,8, 0,1,2,3,4,5,6,7, 7,6,5,4,3,2,1,0, 8,9,10,11,12,13,14,15, 19,18,17,16}
};
bool ball_enabled = false;
uint8_t ball_size = 1;
uint8_t ball_pos = 0;
@@ -103,13 +109,15 @@ namespace tia
void tick()
{
if (!vblank_dump) current_frame_cycles++;
color_clock = (color_clock + 1) % 228;
color_clock = (color_clock + 1) % 228;
if (color_clock == 0) {
rdy_low = false;
current_scanline++;
}
audio::tick();
// [TODO] Pintar pixels
}
@@ -240,6 +248,27 @@ namespace tia
case 0x14: // RESBL
ball_pos = color_clock < 68 ? 0 : color_clock; break;
case 0x15: // AUDC0
case 0x16: // AUDC1
case 0x17: // AUDF0
case 0x18: // AUDF1
case 0x19: // AUDV0
case 0x1A: // AUDV1
audio::write(address, value);
break;
case 0x1B: //GRP0
player0_bits = value; break;
case 0x1C: //GRP1
player1_bits = value; break;
case 0x1D: //ENAM0
missile0_enabled = value & 0x02; break;
case 0x1E: //ENAM1
missile0_enabled = value & 0x02; break;
case 0x1F: //ENABL
missile0_enabled = value & 0x02; break;
}
}
@@ -248,4 +277,5 @@ namespace tia
{
return rdy_low;
}
}
+6
View File
@@ -9,4 +9,10 @@ namespace tia
void write(uint16_t address, uint8_t value);
bool is_rdy_low();
namespace audio
{
uint8_t get_sample0();
uint8_t get_sample1();
}
}