diff --git a/atari2600.cpp b/atari2600.cpp index 567a0e4..8ce4f93 100644 --- a/atari2600.cpp +++ b/atari2600.cpp @@ -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); } diff --git a/speaker.cpp b/speaker.cpp new file mode 100644 index 0000000..c3b188e --- /dev/null +++ b/speaker.cpp @@ -0,0 +1,88 @@ +#include "speaker.h" +#include +#include +#include + +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 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); + } + } + } +} diff --git a/speaker.h b/speaker.h index 973e8ee..1e68b7b 100644 --- a/speaker.h +++ b/speaker.h @@ -3,7 +3,7 @@ namespace speaker { - void init(); + void init(float clock_frequency); void quit(); void enable(); void disable(); diff --git a/tia-audio.cpp b/tia-audio.cpp new file mode 100644 index 0000000..f7c938d --- /dev/null +++ b/tia-audio.cpp @@ -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; + } + + } +} \ No newline at end of file diff --git a/tia-audio.h b/tia-audio.h new file mode 100644 index 0000000..cef122c --- /dev/null +++ b/tia-audio.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace tia +{ + namespace audio + { + void tick(); + void write(uint16_t address, uint8_t value); + uint8_t get_sample(uint8_t channel); + } +} \ No newline at end of file diff --git a/tia.cpp b/tia.cpp index 8871479..453c456 100644 --- a/tia.cpp +++ b/tia.cpp @@ -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,7 +248,28 @@ 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; } + } diff --git a/tia.h b/tia.h index 464ac49..6973e1d 100644 --- a/tia.h +++ b/tia.h @@ -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(); + } }