- [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:
+8
-2
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace speaker
|
||||
{
|
||||
void init();
|
||||
void init(float clock_frequency);
|
||||
void quit();
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
+188
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user