401 lines
12 KiB
C
401 lines
12 KiB
C
#include <audio.h>
|
|
#include <math.h>
|
|
#include <windows.h>
|
|
|
|
#include <portaudio.h>
|
|
|
|
#define SAMPLE_RATE 44100
|
|
#define FRAMES_PER_BUFFER 64
|
|
#define TABLE_SIZE 44100
|
|
|
|
#define M_PI 3.14159265
|
|
|
|
static float sine[TABLE_SIZE];
|
|
static unsigned long ind = 0;
|
|
static unsigned long ind2 = 0;
|
|
|
|
static int sq1_on_time;
|
|
static int sq1_off_time;
|
|
static float sq1 = 1;
|
|
static int sq1_timer = 0;
|
|
|
|
static int sq2_on_time;
|
|
static int sq2_off_time;
|
|
static float sq2 = 1;
|
|
static int sq2_timer = 0;
|
|
|
|
static audio_context ctx;
|
|
|
|
HANDLE data_to_read;
|
|
HANDLE data_to_write;
|
|
|
|
const u8 square_sample[8] = {
|
|
0x0,
|
|
0x0,
|
|
0x0,
|
|
0x0,
|
|
0x1,
|
|
0x1,
|
|
0x1,
|
|
0x1
|
|
};
|
|
|
|
static int audio_callback(const void* input_uffer, void *output_buffer,
|
|
unsigned long framesPerBuffer,
|
|
const PaStreamCallbackTimeInfo *time_info,
|
|
PaStreamCallbackFlags status_flags,
|
|
void *userData ) {
|
|
//printf("Audio Callback!\n");
|
|
//audio_context *ctx = (audio_context *) userData;
|
|
float *out = (float *)output_buffer;
|
|
//static float last = 0;
|
|
//float val;
|
|
//if(!ctx.ready) {
|
|
// val = last;
|
|
//} else {
|
|
// val = ctx.sq1_value;
|
|
// last = val;
|
|
// ctx.ready = false;
|
|
//}
|
|
//PaTime time = time_info->currentTime;
|
|
//sq1_on_time = 1/(ctx->sq1_freq/(float)SAMPLE_RATE) * ctx->sq1_duty;
|
|
//sq1_off_time = 1/(ctx->sq1_freq/(float)SAMPLE_RATE) * (1 - ctx->sq1_duty);
|
|
|
|
//sq2_on_time = 1/(ctx->sq2_freq/(float)SAMPLE_RATE) * ctx->sq2_duty;
|
|
//sq2_off_time = 1/(ctx->sq2_freq/(float)SAMPLE_RATE) * (1 - ctx->sq2_duty);
|
|
//ctx.sq1_period_timer++;
|
|
|
|
//if(ctx.sq1_period_timer >= 0x800) {
|
|
//ctx.sq1_period_timer = ctx.sq1_period_reset;
|
|
//ctx.sq1_sample = (ctx.sq1_sample + 1) % 8;
|
|
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
|
|
//ctx.sq1_value = - ctx.sq1_value;
|
|
//}
|
|
//printf("val: %f\n", val);
|
|
float left = 0;
|
|
float right = 0;
|
|
for(int i = 0; i < framesPerBuffer; i++) {
|
|
if(!ctx.audio_enabled) {
|
|
*out++ = 0;
|
|
*out++ = 0;
|
|
continue;
|
|
}
|
|
if(WaitForSingleObject(data_to_read, INFINITE) == WAIT_TIMEOUT) {
|
|
continue;
|
|
}
|
|
if(ctx.sq1_write_head != ctx.sq1_read_head) {
|
|
if(ctx.ch1_left){
|
|
//left = ((float)(ctx.sq1_volume * ctx.sq1_audio_buffer[ctx.sq1_read_head])/15.0f) - 0.5f;
|
|
}
|
|
if(ctx.ch1_right){
|
|
//right = ((float)(ctx.sq1_volume * ctx.sq1_audio_buffer[ctx.sq1_read_head])/15.0f) - 0.5f;
|
|
}
|
|
ctx.sq1_read_head++;
|
|
if (ctx.sq1_read_head >= 4096) {
|
|
ctx.sq1_read_head = 0;
|
|
}
|
|
}
|
|
|
|
//if(ctx.sq2_write_head != ctx.sq2_read_head) {
|
|
if(ctx.ch2_left){
|
|
left = ctx.sq2_audio_buffer[ctx.sq2_read_head];
|
|
//printf("audio: %d\n", ctx.sq2_audio_buffer[ctx.sq2_read_head]);
|
|
//left = ((float)(ctx.sq2_volume * square_sample[ctx.sq2_sample])/15.0f) - 0.5f;
|
|
}
|
|
if(ctx.ch2_right){
|
|
//right += ((float)(ctx.sq2_volume * ctx.sq2_audio_buffer[ctx.sq2_read_head])/15.0f) - 0.5f;
|
|
}
|
|
ctx.sq2_read_head++;
|
|
if (ctx.sq2_read_head >= 4096) {
|
|
ctx.sq2_read_head = 0;
|
|
}
|
|
//}
|
|
left *= (float)ctx.volume_left/8.0f;
|
|
right *= (float)ctx.volume_right/8.0f;
|
|
*out++ = left;
|
|
*out++ = right;
|
|
ReleaseSemaphore(data_to_write, 1, NULL);
|
|
}
|
|
return paContinue;
|
|
}
|
|
|
|
const int semaphore_count = 128;
|
|
static PaStream *stream;
|
|
|
|
void audio_init(){
|
|
data_to_read = CreateSemaphore(NULL, 0, semaphore_count, NULL);
|
|
data_to_write = CreateSemaphore(NULL, semaphore_count, semaphore_count, NULL);
|
|
PaStreamParameters output_parameters;
|
|
PaError err;
|
|
|
|
ctx.sq1_volume = 1;
|
|
ctx.sq1_duty = 0.5;
|
|
ctx.sq1_freq = 200;
|
|
|
|
ctx.sq2_volume = 1;
|
|
ctx.sq2_duty = 0.5;
|
|
ctx.sq2_freq = 400;
|
|
ctx.audio_enabled = true;
|
|
|
|
ctx.sq2_period_reset = 0x700;
|
|
ctx.sq2_period_timer = 0x700;
|
|
ctx.sq2_enable = true;
|
|
ctx.sq2_len_enable = false;
|
|
ctx.ch2_left = true;
|
|
ctx.volume_left = 8;
|
|
ctx.sq2_volume = 1;
|
|
ctx.sq1_value = -1;
|
|
ctx.sq1_write_head = 0;
|
|
ctx.sq1_read_head = 0;
|
|
ctx.sq2_read_head = 0;
|
|
ctx.sq2_write_head = 0;
|
|
ctx.sq2_env_pace = 0;
|
|
|
|
for(int i = 0; i < TABLE_SIZE; i++) {
|
|
sine[i] = (float) sin(((double)i/(double)TABLE_SIZE) * M_PI * 2.);
|
|
}
|
|
|
|
err = Pa_Initialize();
|
|
if (err != paNoError) goto error;
|
|
output_parameters.device = Pa_GetDefaultOutputDevice();
|
|
if(output_parameters.device == paNoDevice) {
|
|
fprintf(stderr, "No default audio device!\n");
|
|
goto error;
|
|
}
|
|
output_parameters.channelCount = 2;
|
|
output_parameters.sampleFormat = paFloat32;
|
|
output_parameters.suggestedLatency = Pa_GetDeviceInfo(output_parameters.device)->defaultLowOutputLatency;
|
|
output_parameters.hostApiSpecificStreamInfo = NULL;
|
|
|
|
err = Pa_OpenStream(&stream,
|
|
NULL,
|
|
&output_parameters,
|
|
SAMPLE_RATE,
|
|
FRAMES_PER_BUFFER,
|
|
paClipOff,
|
|
audio_callback,
|
|
&ctx);
|
|
if(err != paNoError) goto error;
|
|
err = Pa_StartStream(stream);
|
|
if(err != paNoError) goto error;
|
|
return;
|
|
error:
|
|
Pa_Terminate();
|
|
fprintf(stderr, "portaudio stream error\n\tError Number: %d\n\tError Message: %s\n", err, Pa_GetErrorText(err));
|
|
}
|
|
|
|
static int change = 1;
|
|
static u32 ticks = 0;
|
|
|
|
void audio_tick(){
|
|
u32 prev_ticks = ticks;
|
|
ticks++;
|
|
if(!(ticks & 0b1)) {
|
|
if(ctx.sq1_len_enable) {
|
|
ctx.sq1_len++;
|
|
if(ctx.sq1_len >= 64) {
|
|
ctx.sq1_enable = false;
|
|
}
|
|
}
|
|
|
|
if(ctx.sq2_len_enable) {
|
|
ctx.sq2_len++;
|
|
if(ctx.sq2_len >= 64) {
|
|
ctx.sq2_enable = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if((prev_ticks & (1 << 3)) && !(ticks & (1 << 3))) {
|
|
if(ctx.sq1_env_pace != 0){
|
|
ctx.sq1_env_timer++;
|
|
if(ctx.sq1_env_timer >= ctx.sq1_env_pace) {
|
|
ctx.sq1_volume += ctx.sq1_env_direction ? 1 : -1;
|
|
if(ctx.sq1_volume < 0)
|
|
ctx.sq1_volume = 0;
|
|
if(ctx.sq1_volume > 7)
|
|
ctx.sq1_volume = 7;
|
|
ctx.sq1_env_timer = 0;
|
|
}
|
|
}
|
|
|
|
if(ctx.sq2_env_pace != 0){
|
|
ctx.sq2_env_timer++;
|
|
if(ctx.sq2_env_timer >= ctx.sq2_env_pace) {
|
|
ctx.sq2_volume += ctx.sq2_env_direction ? 1 : -1;
|
|
if(ctx.sq2_volume < 0)
|
|
ctx.sq2_volume = 0;
|
|
if(ctx.sq2_volume > 7)
|
|
ctx.sq2_volume = 7;
|
|
ctx.sq2_env_timer = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static u32 updates;
|
|
static u32 last;
|
|
|
|
void audio_period_tick(){
|
|
//DWORD dwaitResult = WaitForSingleObject(mutex, INFINITE);
|
|
//if(dwaitResult == WAIT_ABANDONED)
|
|
// return;
|
|
u32 now = get_ticks();
|
|
if(now - last >= 1000) {
|
|
printf("Updates: %d\n", updates);
|
|
updates = 0;
|
|
last = now;
|
|
}
|
|
|
|
ctx.sq1_period_timer++;
|
|
ctx.sq2_period_timer++;
|
|
|
|
if(ctx.sq1_period_timer >= 0x800) {
|
|
ctx.sq1_period_timer = ctx.sq1_period_reset;
|
|
ctx.sq1_sample = (ctx.sq1_sample + 1) % 8;
|
|
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
|
|
//ctx.ready = true;
|
|
}
|
|
if(ctx.sq2_period_timer >= 0x800) {
|
|
ctx.sq2_period_timer = ctx.sq2_period_reset;
|
|
ctx.sq2_sample = (ctx.sq2_sample + 1) % 8;
|
|
//ctx.sq1_value = (float)square_sample[ctx.sq1_sample];///15.0f;
|
|
//ctx.ready = true;
|
|
if(ctx.sq2_sample == 0) {
|
|
updates++;
|
|
}
|
|
}
|
|
DWORD result = WaitForSingleObject(data_to_write, 0);
|
|
if(result != WAIT_TIMEOUT) {
|
|
//updates++;
|
|
//updates = 0;
|
|
//u32 read = ctx.read_head;
|
|
//u32 write = ctx.write_head;
|
|
//if(read > write) {
|
|
// write += 4096;
|
|
//}
|
|
//if(write - read <= 64){
|
|
// return;
|
|
//}
|
|
if(ctx.sq1_enable){
|
|
ctx.sq1_audio_buffer[ctx.sq1_write_head++] = ((float)(ctx.sq2_volume * square_sample[ctx.sq1_sample])/7.5f) - 1.0f;
|
|
|
|
} else {
|
|
ctx.sq1_audio_buffer[ctx.sq1_write_head++] = 0x0;
|
|
}
|
|
if(ctx.sq1_write_head >= 4096) {
|
|
ctx.sq1_write_head = 0;
|
|
}
|
|
if(ctx.sq2_enable){
|
|
ctx.sq2_audio_buffer[ctx.sq2_write_head++] = ((float)(ctx.sq2_volume * (square_sample[ctx.sq2_sample] - .5f))/7.5f);
|
|
//printf("Audio: %f\n", ctx.sq2_audio_buffer[ctx.sq2_write_head - 1]);
|
|
} else {
|
|
ctx.sq2_audio_buffer[ctx.sq2_write_head++] = 0.0f;
|
|
}
|
|
if(ctx.sq2_write_head >= 4096) {
|
|
ctx.sq2_write_head = 0;
|
|
}
|
|
ReleaseSemaphore(data_to_read, 1, NULL);
|
|
}
|
|
//ReleaseMutex(mutex);
|
|
}
|
|
|
|
void enable_square1() {
|
|
ctx.sq1_enable = true;
|
|
if(ctx.sq1_len >= 64) {
|
|
ctx.sq1_len = ctx.sq1_initial_len;
|
|
}
|
|
ctx.sq1_volume = ctx.sq1_initial_volume;
|
|
ctx.sq1_env_timer = 0;
|
|
}
|
|
|
|
void enable_square2() {
|
|
ctx.sq2_enable = true;
|
|
if(ctx.sq2_len >= 64) {
|
|
ctx.sq2_len = ctx.sq2_initial_len;
|
|
}
|
|
ctx.sq2_volume = ctx.sq2_initial_volume;
|
|
ctx.sq2_env_timer = 0;
|
|
ctx.sq2_sample = 0;
|
|
|
|
//printf("Channel 2 enabled, Timer: %d\n", ctx.sq2_len_enable);
|
|
}
|
|
|
|
u8 audio_read(u16 address) {
|
|
return 0x00;
|
|
}
|
|
|
|
void audio_write(u16 address, u8 value){
|
|
return;
|
|
if(address == 0xFF26) {
|
|
ctx.audio_enabled = value & 0x80;
|
|
}
|
|
if(address == 0xFF25) {
|
|
ctx.ch4_left = value & 0b10000000;
|
|
ctx.ch3_left = value & 0b01000000;
|
|
ctx.ch2_left = value & 0b00100000;
|
|
ctx.ch1_left = value & 0b00010000;
|
|
|
|
ctx.ch4_right = value & 0b00001000;
|
|
ctx.ch3_right = value & 0b00000100;
|
|
ctx.ch2_right = value & 0b00000010;
|
|
ctx.ch1_right = value & 0b00000001;
|
|
}
|
|
|
|
if(address == 0xFF24) {
|
|
ctx.volume_left = (value >> 4) & 0b111;
|
|
if(ctx.volume_left == 0) ctx.volume_left = 1;
|
|
ctx.volume_right = value & 0b111;
|
|
if(ctx.volume_right == 0) ctx.volume_right = 1;
|
|
}
|
|
|
|
if(address == 0xFF10) {
|
|
ctx.sq1_sweep_pace = (value >> 4) & 0b111;
|
|
ctx.sq1_sweep_direction = value & 0x08;
|
|
ctx.sq1_sweep_step = value & 0b111;
|
|
}
|
|
if(address == 0xFF11) {
|
|
ctx.sq1_duty = value >> 6;
|
|
ctx.sq1_initial_len = value & 0x3F;
|
|
}
|
|
if(address == 0xFF12) {
|
|
ctx.sq1_initial_volume = value >> 4;
|
|
ctx.sq1_env_direction = value & 0x8;
|
|
ctx.sq1_env_pace = value & 0b111;
|
|
}
|
|
if(address == 0xFF13) {
|
|
ctx.sq1_period_reset = (ctx.sq1_period_reset & 0xF0) | value;
|
|
}
|
|
if(address == 0xFF14) {
|
|
ctx.sq1_period_reset = (ctx.sq1_period_reset & 0x0F) | ((value & 0b111) << 8);
|
|
ctx.sq1_len_enable = value & 0x40;
|
|
if(value & 0x80) {
|
|
enable_square1();
|
|
}
|
|
}
|
|
|
|
|
|
if(address == 0xFF16) {
|
|
ctx.sq2_duty = value >> 6;
|
|
ctx.sq2_initial_len = value & 0x3F;
|
|
}
|
|
if(address == 0xFF17) {
|
|
ctx.sq2_initial_volume = value >> 4;
|
|
ctx.sq2_env_direction = value & 0x8;
|
|
ctx.sq2_env_pace = value * 0b111;
|
|
if(!ctx.sq2_env_direction && !ctx.sq2_initial_volume) {
|
|
ctx.sq2_enable = false;
|
|
}
|
|
}
|
|
if(address == 0xFF18) {
|
|
ctx.sq2_period_reset = (ctx.sq2_period_reset & 0xF0) | value;
|
|
}
|
|
if(address == 0xFF19) {
|
|
ctx.sq2_period_reset = (ctx.sq2_period_reset & 0x0F) | ((value & 0b111) << 8);
|
|
ctx.sq2_len_enable = value & 0x40;
|
|
if(value & 0x80) {
|
|
enable_square2();
|
|
}
|
|
}
|
|
} |