OXYGENHUB
# 8799a5e4565e x16-emulator / src / wav_recorder.c
5.2 KiB · C · last changed chore(): initial commit 15 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2022 Stephen Horn
3
// All rights reserved. License: 2-clause BSD
4
5
#include "wav_recorder.h"
6
7
#include "SDL.h"
8
#include "audio.h"
9
#include "glue.h"
10
#include <string.h>
11
#include <stdlib.h>
12
#include <stdio.h>
13
14
#pragma pack(push, 1)
15
typedef struct {
16
	char     chunk_id[4];
17
	uint32_t size;
18
	char     wave_id[4];
19
} riff_chunk;
20
21
typedef struct {
22
	char     chunk_id[4];
23
	uint32_t size;
24
	uint16_t format_tag;
25
	uint16_t channels;
26
	uint32_t samples_per_sec;
27
	uint32_t bytes_per_sec;
28
	uint16_t block_align;
29
	uint16_t bits_per_sample;
30
} fmt_chunk;
31
32
typedef struct {
33
	char     chunk_id[4];
34
	uint32_t size;
35
} data_chunk;
36
37
typedef struct {
38
	riff_chunk riff;
39
	fmt_chunk  fmt;
40
	data_chunk data;
41
} file_header;
42
#pragma pack(pop)
43
44
static SDL_RWops * wav_file = NULL;
45
static file_header wav_header;
46
static uint32_t    wav_samples_written = 0;
47
48
static void
49
wav_init_file_header(file_header *header)
50
{
51
	header->riff.chunk_id[0]    = 'R';
52
	header->riff.chunk_id[1]    = 'I';
53
	header->riff.chunk_id[2]    = 'F';
54
	header->riff.chunk_id[3]    = 'F';
55
	header->riff.size           = 4;
56
	header->riff.wave_id[0]     = 'W';
57
	header->riff.wave_id[1]     = 'A';
58
	header->riff.wave_id[2]     = 'V';
59
	header->riff.wave_id[3]     = 'E';
60
	header->fmt.chunk_id[0]     = 'f';
61
	header->fmt.chunk_id[1]     = 'm';
62
	header->fmt.chunk_id[2]     = 't';
63
	header->fmt.chunk_id[3]     = ' ';
64
	header->fmt.size            = 16;
65
	header->fmt.format_tag      = 0x0001; // WAVE_FORMAT_PCM
66
	header->fmt.channels        = 2;
67
	header->fmt.samples_per_sec = 0;
68
	header->fmt.bytes_per_sec   = 0;
69
	header->fmt.block_align     = 0;
70
	header->fmt.bits_per_sample = 16 * 2;
71
	header->data.chunk_id[0]    = 'd';
72
	header->data.chunk_id[1]    = 'a';
73
	header->data.chunk_id[2]    = 't';
74
	header->data.chunk_id[3]    = 'a';
75
	header->data.size           = 0;
76
}
77
78
static void
79
wav_update_sizes()
80
{
81
	wav_header.data.size = sizeof(int16_t) * wav_header.fmt.channels * wav_samples_written;
82
	wav_header.riff.size = 4 + sizeof(fmt_chunk) + sizeof(data_chunk) + (wav_header.data.size);
83
}
84
85
static void
86
wav_begin(const char *path, int32_t sample_rate)
87
{
88
	wav_file = SDL_RWFromFile(path, "wb");
89
	if (wav_file) {
90
		wav_init_file_header(&wav_header);
91
		wav_header.fmt.samples_per_sec = sample_rate;
92
		wav_header.fmt.bytes_per_sec   = sample_rate * sizeof(int16_t) * wav_header.fmt.channels;
93
		wav_header.fmt.block_align     = sizeof(int16_t) * wav_header.fmt.channels;
94
		wav_header.fmt.bits_per_sample = (sizeof(int16_t)) << 3;
95
96
		const size_t written = SDL_RWwrite(wav_file, &wav_header, sizeof(file_header), 1);
97
		if (written == 0) {
98
			SDL_RWclose(wav_file);
99
			wav_file = NULL;
100
		}
101
	}
102
}
103
104
static void
105
wav_end()
106
{
107
	if (wav_file != NULL) {
108
		wav_update_sizes();
109
		SDL_RWseek(wav_file, 0, RW_SEEK_SET);
110
		SDL_RWwrite(wav_file, &wav_header, sizeof(file_header), 1);
111
		SDL_RWclose(wav_file);
112
		wav_file = NULL;
113
	}
114
}
115
116
static void
117
wav_add(const int16_t *samples, const int num_samples)
118
{
119
	if (wav_file) {
120
		const size_t bytes   = sizeof(int16_t) * 2 * num_samples;
121
		const size_t written = SDL_RWwrite(wav_file, samples, bytes, 1);
122
		if (written == 0) {
123
			SDL_RWclose(wav_file);
124
			wav_file = NULL;
125
		} else {
126
			wav_samples_written += num_samples;
127
		}
128
	}
129
}
130
131
// WAV recorder states
132
typedef enum {
133
	RECORD_WAV_DISABLED = 0,
134
	RECORD_WAV_PAUSED,
135
	RECORD_WAV_AUTOSTARTING,
136
	RECORD_WAV_RECORDING
137
} wav_recorder_state_t;
138
139
static wav_recorder_state_t Wav_record_state = RECORD_WAV_DISABLED;
140
static char *               Wav_path         = NULL;
141
142
void
143
wav_recorder_shutdown()
144
{
145
	if (Wav_record_state == RECORD_WAV_RECORDING) {
146
		wav_end();
147
	}
148
}
149
150
void
151
wav_recorder_process(const int16_t *samples, const int num_samples)
152
{
153
	int i;
154
	if (Wav_record_state == RECORD_WAV_AUTOSTARTING) {
155
		for (i = 0; i < num_samples; ++i) {
156
			if (samples[i] != 0) {
157
				Wav_record_state = RECORD_WAV_RECORDING;
158
				wav_begin(Wav_path, host_sample_rate);
159
				break;
160
			}
161
		}
162
	}
163
164
	if (Wav_record_state == RECORD_WAV_RECORDING) {
165
		wav_add(samples, num_samples);
166
	}
167
}
168
169
void
170
wav_recorder_set(wav_recorder_command_t command)
171
{
172
	if (Wav_record_state != RECORD_WAV_DISABLED) {
173
		switch (command) {
174
			case RECORD_WAV_PAUSE:
175
				Wav_record_state = RECORD_WAV_PAUSED;
176
				break;
177
			case RECORD_WAV_RECORD:
178
				Wav_record_state = RECORD_WAV_RECORDING;
179
				wav_begin(Wav_path, host_sample_rate);
180
				break;
181
			case RECORD_WAV_AUTOSTART:
182
				Wav_record_state = RECORD_WAV_AUTOSTARTING;
183
				break;
184
			default:
185
				printf("Unknown command %d passed to wav_recorder_set.\n", (int)command);
186
				break;
187
		}
188
	}
189
}
190
191
uint8_t
192
wav_recorder_get_state()
193
{
194
	return (uint8_t)Wav_record_state;
195
}
196
197
void
198
wav_recorder_set_path(const char *path)
199
{
200
	if (Wav_record_state == RECORD_WAV_RECORDING) {
201
		wav_end();
202
	}
203
204
	if (Wav_path != NULL) {
205
		free(Wav_path);
206
		Wav_path = NULL;
207
	}
208
209
	if (path != NULL) {
210
		Wav_path = malloc(sizeof(char) * (strlen(path) + 1));
211
		strcpy(Wav_path, path);
212
213
		if (!strcmp(Wav_path + strlen(Wav_path) - 5, ",wait")) {
214
			Wav_path[strlen(Wav_path) - 5] = 0;
215
			Wav_record_state               = RECORD_WAV_PAUSED;
216
		} else if (!strcmp(Wav_path + strlen(Wav_path) - 5, ",auto")) {
217
			Wav_path[strlen(Wav_path) - 5] = 0;
218
			Wav_record_state               = RECORD_WAV_AUTOSTARTING;
219
		} else {
220
			Wav_record_state = RECORD_WAV_RECORDING;
221
			wav_begin(Wav_path, host_sample_rate);
222
		}
223
	} else {
224
		Wav_record_state = RECORD_WAV_DISABLED;
225
	}
226
}