Browse Source

[sdl] add audio capture, rework audio logic a bit

master
asie 2 weeks ago
parent
commit
8bbeae3ba7
10 changed files with 371 additions and 50 deletions
  1. 28
    4
      src/audio_stream.c
  2. 2
    0
      src/audio_stream.h
  3. 170
    0
      src/audio_writer.c
  4. 31
    0
      src/audio_writer.h
  5. 1
    12
      src/screenshot_writer.c
  6. 53
    32
      src/sdl/frontend.c
  7. 55
    0
      src/util.c
  8. 29
    0
      src/util.h
  9. 1
    1
      zeta_sdl.sh
  10. 1
    1
      zeta_sdl_mingw.sh

+ 28
- 4
src/audio_stream.c View File

@@ -57,6 +57,10 @@ void audio_stream_init(long time, int freq) {
audio_freq = freq;
}

u8 audio_stream_get_volume() {
return audio_volume;
}

double audio_stream_get_note_delay() {
return audio_delay_time;
}
@@ -76,7 +80,8 @@ void audio_stream_set_volume(u8 volume) {

void audio_stream_generate_u8(long time, u8 *stream, int len) {
int i; long j;
float freq_samples;
int freq_samples_fixed;
int pos_samples_fixed;
int k;
double audio_curr_time = audio_prev_time + (len / (double) audio_freq * 1000);
// double audio_curr_time = time;
@@ -97,7 +102,9 @@ void audio_stream_generate_u8(long time, u8 *stream, int len) {
audio_curr_time = time;
}

// fprintf(stderr, "audiogen time %.2f realtime %ld drift %.2f\n", audio_curr_time, time, time - audio_curr_time);
#ifdef AUDIO_STREAM_DEBUG
fprintf(stderr, "[callback] expected time %.2f received time %ld drift %.2f\n", audio_curr_time, time, time - audio_curr_time);
#endif

if (speaker_entry_pos == 0) {
memset(stream, 128, len);
@@ -111,6 +118,12 @@ void audio_stream_generate_u8(long time, u8 *stream, int len) {
audio_from = (long) (audio_dfrom * res_to_samples);
audio_to = (long) (audio_dto * res_to_samples);

#ifdef AUDIO_STREAM_DEBUG
if (speaker_entries[i].enabled) {
printf("[callback] testing note @ %.2f Hz (%ld, %ld)\n", speaker_entries[i].freq, audio_from, audio_to);
}
#endif

if (audio_from < 0) audio_from = 0;
else if (audio_from >= len) break;
if (audio_to < 0) audio_to = 0;
@@ -119,9 +132,17 @@ void audio_stream_generate_u8(long time, u8 *stream, int len) {
// emit
if (audio_to > audio_from) {
if (speaker_entries[i].enabled) {
freq_samples = (float) (audio_freq / (speaker_entries[i].freq * 2));
#ifdef AUDIO_STREAM_DEBUG
printf("[callback] emitting note @ %.2f Hz\n", speaker_entries[i].freq);
#endif
freq_samples_fixed = (int) ((audio_freq << 8) / (speaker_entries[i].freq * 2));
pos_samples_fixed = (speaker_freq_ctr << 8) % (freq_samples_fixed << 1);
for (j = audio_from; j < audio_to; j++) {
stream[j] = (((long) ((speaker_freq_ctr + j - audio_from) / freq_samples)) & 1) ? (128-audio_volume) : (128+audio_volume);
if ((pos_samples_fixed & 0xFFFFFF00) < (freq_samples_fixed & 0xFFFFFF00))
stream[j] = 128+audio_volume;
else
stream[j] = 128-audio_volume;
pos_samples_fixed = (pos_samples_fixed + 256) % (freq_samples_fixed << 1);
}
speaker_freq_ctr += audio_to - audio_from;
} else {
@@ -174,6 +195,9 @@ void audio_stream_append_off(long time) {
return;
}

if (time < audio_prev_time)
time = audio_prev_time;

speaker_entries[speaker_entry_pos].ms = time;

if (speaker_entry_pos > 0) {

+ 2
- 0
src/audio_stream.h View File

@@ -25,6 +25,8 @@
USER_FUNCTION
void audio_stream_init(long time, int freq);
USER_FUNCTION
u8 audio_stream_get_volume();
USER_FUNCTION
u8 audio_stream_get_max_volume();
USER_FUNCTION
void audio_stream_set_volume(u8 volume);

+ 170
- 0
src/audio_writer.c View File

@@ -0,0 +1,170 @@
/**
* Copyright (c) 2018, 2019 Adrian Siekierka
*
* This file is part of Zeta.
*
* Zeta is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zeta 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zeta. If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "util.h"
#include "audio_writer.h"

#define AUDIO_MIN_SAMPLE 25
#define AUDIO_MAX_SAMPLE 231

static FILE *audio_file;
static char *audio_vbuf;
static double audio_time_offset;
static int audio_freq;

// states
typedef struct {
u8 enabled;
double freq;
long ms;
} audio_writer_entry;

static audio_writer_entry* audio_entries;
static u32 audio_entries_size, audio_entries_count;
static int audio_note_counter;
static u8 audio_note_enabled; // last
static double audio_note_freq; // last

static audio_writer_entry* audio_writer_allocate_entry(void) {
if (audio_entries_count >= audio_entries_size) {
if (audio_entries_size == 0) {
audio_entries_size = 4096;
audio_entries = malloc(sizeof(audio_writer_entry) * audio_entries_size);
} else {
audio_entries_size *= 2;
audio_entries = realloc(audio_entries, sizeof(audio_writer_entry) * audio_entries_size);
}
}

return &(audio_entries[audio_entries_count++]);
}

int audio_writer_start(const char *filename, long time, int freq) {
if (audio_file != NULL) return -1;
audio_file = fopen(filename, "wb");
if (audio_file == NULL) return -1;
setvbuf(audio_file, audio_vbuf, _IOFBF, 65536);
// clean up fields
audio_entries = NULL;
audio_entries_size = 0;
audio_entries_count = 0;
audio_note_counter = 0;
audio_note_enabled = 0;
audio_note_freq = 0.0;
audio_time_offset = time;
audio_freq = freq;
// write header
fwrite("RIFF", 4, 1, audio_file);
fput32le(audio_file, 0); // file size - 8, see audio_writer_stop
fwrite("WAVE", 4, 1, audio_file);
// - fmt
fwrite("fmt ", 4, 1, audio_file);
fput32le(audio_file, 16); // chunk size
fput16le(audio_file, 1); // format (PCM)
fput16le(audio_file, 1); // channels (1)
fput32le(audio_file, freq); // frequency
fput32le(audio_file, freq); // bytes per second
fput16le(audio_file, 1); // bytes per full sample
fput16le(audio_file, 8); // bits per sample
// - data
fwrite("data", 4, 1, audio_file);
fput32le(audio_file, 0); // file size - 44, see audio_writer_stop
return 0;
}

static void audio_writer_advance(long time, u8 enabled, double freq) {
long i, samples;
int freq_samples_fixed, pos_samples_fixed;

if (audio_note_enabled) {
long shortest_time = audio_time_offset + audio_stream_get_note_delay();
if (time < shortest_time) {
time = shortest_time;
}
}
// write [audio_time_offset..time] with audio_note values
// rounded, as we adjust audio_time_offset based on this
samples = (long) (((time - audio_time_offset) * audio_freq / 1000.0) + 0.5);
if (audio_note_enabled) {
// see audio_stream.c
freq_samples_fixed = (int) ((audio_freq << 8) / (audio_note_freq * 2));
pos_samples_fixed = (audio_note_counter << 8) % (freq_samples_fixed << 1);
for (i = 0; i < samples; i++) {
if ((pos_samples_fixed & 0xFFFFFF00) < (freq_samples_fixed & 0xFFFFFF00))
fputc(AUDIO_MAX_SAMPLE, audio_file);
else
fputc(AUDIO_MIN_SAMPLE, audio_file);
pos_samples_fixed = (pos_samples_fixed + 256) % (freq_samples_fixed << 1);
}
} else {
// write silence
for (i = 0; i < samples; i++)
fputc(128, audio_file);
}
// set new note as last
// audio_time_offset = time;
audio_time_offset += (samples * 1000.0 / audio_freq);
audio_note_enabled = enabled;
audio_note_freq = freq;
if (!enabled) audio_note_counter = 0;
else audio_note_counter += samples;
}

void audio_writer_stop(long time) {
audio_writer_entry *e;
audio_writer_speaker_off(time);
// write... uhh... all data
e = audio_entries;
for (int i = 0; i < audio_entries_count; i++, e++) {
audio_writer_advance(e->ms, e->enabled, e->freq);
}
// get filesize
fseek(audio_file, 0, SEEK_END);
int filesize = (int) ftell(audio_file);
// fix WAV sizes
fseek(audio_file, 4, SEEK_SET);
fput32le(audio_file, filesize - 8);
fseek(audio_file, 40, SEEK_SET);
fput32le(audio_file, filesize - 44);
// clean up
fclose(audio_file);
audio_file = NULL;
free(audio_vbuf);
audio_vbuf = NULL;
free(audio_entries);
audio_entries = NULL;
audio_entries_size = 0;
audio_entries_count = 0;
}

void audio_writer_speaker_on(long time, double freq) {
audio_writer_entry *e = audio_writer_allocate_entry();
e->enabled = 1;
e->freq = freq;
e->ms = time;
}

void audio_writer_speaker_off(long time) {
audio_writer_entry *e = audio_writer_allocate_entry();
e->enabled = 0;
e->ms = time;
}

+ 31
- 0
src/audio_writer.h View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2018, 2019 Adrian Siekierka
*
* This file is part of Zeta.
*
* Zeta is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zeta 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zeta. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __AUDIO_WRITER_H__
#define __AUDIO_WRITER_H__

#include "types.h"
#include "audio_stream.h"

int audio_writer_start(const char *filename, long time, int freq);
void audio_writer_stop(long time);
void audio_writer_speaker_on(long time, double freq);
void audio_writer_speaker_off(long time);

#endif

+ 1
- 12
src/screenshot_writer.c View File

@@ -20,22 +20,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "screenshot_writer.h"

#define POS_MUL (scr_width <= 40 ? 2 : 1)

static void fput16le(FILE *output, int i) {
fputc((i & 0xFF), output);
fputc(((i >> 8) & 0xFF), output);
}

static void fput32le(FILE *output, int i) {
fputc((i & 0xFF), output);
fputc(((i >> 8) & 0xFF), output);
fputc(((i >> 16) & 0xFF), output);
fputc(((i >> 24) & 0xFF), output);
}

#ifdef USE_LIBPNG
#include <png.h>


+ 53
- 32
src/sdl/frontend.c View File

@@ -32,10 +32,12 @@
#include <time.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>
#include "../util.h"
#include "../zzt.h"
#include "../audio_stream.h"
#include "../posix_vfs.h"
#include "../render_software.h"
#include "../audio_writer.h"
#ifdef ENABLE_SCREENSHOTS
#include "../screenshot_writer.h"
#endif
@@ -75,24 +77,31 @@ static SDL_AudioDeviceID audio_device;
static SDL_AudioSpec audio_spec;
static SDL_mutex *audio_mutex;

static double audio_time;
static u8 audio_writer_enabled = 0;

static void audio_callback(void *userdata, Uint8 *stream, int len) {
SDL_LockMutex(audio_mutex);
audio_stream_generate_u8(zeta_time_ms(), stream, len);
SDL_UnlockMutex(audio_mutex);
}

static double audio_time;

void speaker_on(double freq) {
SDL_LockMutex(audio_mutex);
audio_stream_append_on(audio_time, freq);
SDL_UnlockMutex(audio_mutex);
if (audio_writer_enabled) {
audio_writer_speaker_on(audio_time, freq);
}
}

void speaker_off(void) {
SDL_LockMutex(audio_mutex);
audio_stream_append_off(audio_time);
SDL_UnlockMutex(audio_mutex);
if (audio_writer_enabled) {
audio_writer_speaker_off(audio_time);
}
}

static SDL_mutex *zzt_thread_lock;
@@ -379,7 +388,6 @@ int main(int argc, char **argv) {
}
#ifdef ENABLE_SCREENSHOTS
if (event.key.keysym.sym == SDLK_F12) {
int i = -1;
FILE *file;
char filename[24];

@@ -393,43 +401,51 @@ int main(int argc, char **argv) {
if (!video_blink) sflags |= RENDER_BLINK_OFF;
else if (sdl_is_blink_phase(zeta_time_ms())) sflags |= RENDER_BLINK_PHASE;

while ((++i) <= 9999) {
#ifdef USE_LIBPNG
int stype = SCREENSHOT_TYPE_PNG;
snprintf(filename, 23, "screen%d.png", i);
int stype = SCREENSHOT_TYPE_PNG;
file = create_inc_file(filename, 23, "screen%d.png", "wb");
#else
int stype = SCREENSHOT_TYPE_BMP;
snprintf(filename, 23, "screen%d.bmp", i);
int stype = SCREENSHOT_TYPE_BMP;
file = create_inc_file(filename, 23, "screen%d.bmp", "wb");
#endif
file = fopen(filename, "rb");
if (!file) {
file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Could not open file '%s' for writing!\n", filename);
break;
}
if (write_screenshot(
file, stype,
swidth, sflags,
zzt_get_ram() + 0xB8000, charset_update_data,
charw, charh,
palette_update_data
) < 0) {
fprintf(stderr, "Could not write screenshot!\n");
}
fclose(file);
break;
} else {
fclose(file);
if (file != NULL) {
if (write_screenshot(
file, stype,
swidth, sflags,
zzt_get_ram() + 0xB8000, charset_update_data,
charw, charh,
palette_update_data
) < 0) {
fprintf(stderr, "Could not write screenshot!\n");
}
}

if (i > 9999) {
fprintf(stderr, "Could not take screenshot!\n");
fclose(file);
}
break;
}
#endif
if (event.key.keysym.sym == SDLK_F6 && KEYMOD_CTRL(event.key.keysym.mod)) {
// audio writer
if (!audio_writer_enabled) {
FILE *file;
char filename[24];
file = create_inc_file(filename, 23, "audio%d.wav", "wb");
if (file != NULL) {
fclose(file);
if (audio_writer_start(filename, audio_time, 48000) >= 0) {
audio_writer_enabled = 1;
fprintf(stderr, "Audio writing started [%s].\n", filename);
} else {
fprintf(stderr, "Could not start audio writing - internal error!\n");
}
}
} else {
audio_writer_enabled = 0;
audio_writer_stop(audio_time);
fprintf(stderr, "Audio writing stopped.\n");
}
break;
}

if (event.key.keysym.scancode == SDL_SCANCODE_RETURN && KEYMOD_ALT(event.key.keysym.mod)) {
// Alt+ENTER
if (windowed) {
@@ -510,6 +526,11 @@ int main(int argc, char **argv) {
renderer->draw(zzt_vram_copy, blink_mode);
}

if (audio_writer_enabled) {
audio_writer_enabled = 0;
audio_writer_stop(audio_time);
}

zzt_thread_running = 0;
if (audio_device != 0) {
SDL_CloseAudioDevice(audio_device);

+ 55
- 0
src/util.c View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2018, 2019 Adrian Siekierka
*
* This file is part of Zeta.
*
* Zeta is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zeta 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zeta. If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include "util.h"

void fput16le(FILE *output, unsigned short i) {
fputc((i & 0xFF), output);
fputc(((i >> 8) & 0xFF), output);
}

void fput32le(FILE *output, unsigned int i) {
fputc((i & 0xFF), output);
fputc(((i >> 8) & 0xFF), output);
fputc(((i >> 16) & 0xFF), output);
fputc(((i >> 24) & 0xFF), output);
}

FILE *create_inc_file(char *filename, int length, const char *tpl, const char *mode) {
FILE *file;
int i = -1;
while ((++i) <= 9999) {
snprintf(filename, length, tpl, i);
file = fopen(filename, "rb");
if (!file) {
file = fopen(filename, mode);
if (!file) {
fprintf(stderr, "Could not open file '%s' for writing!\n", filename);
return NULL;
}
return file;
} else {
// file exists
fclose(file);
}
}
fprintf(stderr, "Could not generate filename to write to!\n");
return NULL;
}

+ 29
- 0
src/util.h View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2018, 2019 Adrian Siekierka
*
* This file is part of Zeta.
*
* Zeta is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zeta 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Zeta. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __UTIL_H__
#define __UTIL_H__

#include <stdio.h>

void fput16le(FILE *output, unsigned short i);
void fput32le(FILE *output, unsigned int i);
FILE *create_inc_file(char *filename, int length, const char *tpl, const char *mode);

#endif /* __UTIL_H__ */

+ 1
- 1
zeta_sdl.sh View File

@@ -4,6 +4,6 @@ gcc -o build/zeta86 -g -O2 -std=c18 -Wall \
res/8x14.c src/posix_vfs.c src/audio_stream.c \
src/zzt.c src/cpu.c \
src/screenshot_writer.c src/render_software.c \
src/asset_loader.c \
src/asset_loader.c src/util.c src/audio_writer.c \
src/sdl/frontend.c src/sdl/render_software.c src/sdl/render_opengl.c \
-lGL -lSDL2 -lSDL2main -lpng

+ 1
- 1
zeta_sdl_mingw.sh View File

@@ -5,7 +5,7 @@ i686-w64-mingw32-windres mingw/resources.rc build/mingw_resources.o
i686-w64-mingw32-gcc -o build/zeta86.exe -g -O2 -std=c18 -Wall -mwindows \
res/8x14.c src/posix_vfs.c src/audio_stream.c src/zzt.c src/cpu.c \
src/screenshot_writer.c src/render_software.c build/mingw_resources.o \
src/asset_loader.c \
src/asset_loader.c src/util.c src/audio_writer.c \
src/sdl/frontend.c src/sdl/render_software.c src/sdl/render_opengl.c \
-Wl,-Bstatic -lmingw32 -lwinpthread -lgcc -lSDL2main -lpng -lz \
-Wl,-Bdynamic -lSDL2 -lopengl32

Loading…
Cancel
Save