diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/SDL2.c | 17 | ||||
| -rw-r--r-- | src/common/common.c | 8 | ||||
| -rw-r--r-- | src/common/common.h | 1 | ||||
| -rw-r--r-- | src/modules/fluidsynth.c | 56 | ||||
| -rw-r--r-- | src/modules/organya.bin | bin | 0 -> 66152 bytes | |||
| -rw-r--r-- | src/modules/organya.c | 436 | ||||
| -rw-r--r-- | src/portaudio.c | 23 | 
7 files changed, 505 insertions, 36 deletions
@@ -39,21 +39,21 @@ int (*file_ext(char *file))(struct blob *, struct userdata *);  int main(int argc, char **argv) {  	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { -		eprintf("failed to init SDL: %s\n"); +		eprintf("failed to init SDL: %s\n", SDL_GetError());  		return EXIT_FAILURE;  	}  	SDL_StopTextInput();  	if ((window = SDL_CreateWindow("mu-sdl", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN)) == NULL) { -		eprintf("failed to create a window: %s\n"); +		eprintf("failed to create a window: %s\n", SDL_GetError());  		SDL_Quit();  		return EXIT_FAILURE;  	}  	SDL_Renderer *renderer;  	if ((renderer = SDL_CreateRenderer(window, -1, 0)) == NULL) { -		eprintf("failed to create a renderer: %s\n"); +		eprintf("failed to create a renderer: %s\n", SDL_GetError());  		SDL_DestroyWindow(window);  		SDL_Quit();  		return EXIT_FAILURE; @@ -85,7 +85,10 @@ int main(int argc, char **argv) {  			perror(argv[1]);  			goto arg_load_done;  		} -		module_func(&file, &userdata); +		if (module_func(&file, &userdata)) { +			eprintf("%s: failed to load\n", argv[1]); +			userdata.callback = NULL; +		}  	}  	arg_load_done: @@ -133,9 +136,8 @@ int main(int argc, char **argv) {  						free(file.data);  						break;  					} -					SDL_free(evt.drop.file);  					if (module_func(&file, &newuser)) { -						// error +						eprintf("%s: failed to load\n", evt.drop.file);  					} else {  						SDL_LockAudioDevice(audio);  						if (userdata.freefunc != NULL) { @@ -145,9 +147,10 @@ int main(int argc, char **argv) {  						SDL_UnlockAudioDevice(audio);  					}  					free(file.data); +					SDL_free(evt.drop.file);  					break; -				default: +				default:;  			}  		}  	} diff --git a/src/common/common.c b/src/common/common.c index 3fb1c6a..840df50 100644 --- a/src/common/common.c +++ b/src/common/common.c @@ -8,22 +8,24 @@  int (*file_ext(char *file))(struct blob *, struct userdata *) {  	size_t len = strlen(file);  	#define ext(extension) memcmp(file + len - sizeof (extension) + 1, extension, sizeof (extension)) -	if ((ext(".mptm") && ext(".mod") && ext(".MOD") && ext(".xm") && ext(".s3m") && ext(".it")) == 0) { +	if ((ext(".mptm") && ext(".mod") && ext(".MOD") && ext(".xm") && ext(".s3m") && ext(".it") && ext(".oxm") && ext(".mo3")) == 0) {  		return module_openmpt;  	} else if ((ext(".mid") && ext(".MID") && ext(".midi")) == 0) {  		return module_fluidsynth; +	} else if ((ext(".org") && ext(".ORG")) == 0) { +		return module_organya;  	}  	#undef ext  	return NULL;  }  struct blob load_file(char const *const name) { -	const size_t START_SIZE = 1; +	const size_t START_SIZE = 8192;  	FILE *file = fopen(name, "rb");  	if (file == NULL) {  		return (struct blob) {.data = NULL};  	} -	void *data = malloc(START_SIZE); +	char *data = malloc(START_SIZE);  	size_t allocated = START_SIZE;  	size_t used = 0;  	while (1) { diff --git a/src/common/common.h b/src/common/common.h index 806212a..7089c17 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -5,3 +5,4 @@ struct blob load_file(char const *const name);  int module_openmpt(struct blob *file, struct userdata *userdata);  int module_fluidsynth(struct blob *file, struct userdata *userdata); +int module_organya(struct blob *file, struct userdata *userdata); diff --git a/src/modules/fluidsynth.c b/src/modules/fluidsynth.c index 9eedc2c..eeaa795 100644 --- a/src/modules/fluidsynth.c +++ b/src/modules/fluidsynth.c @@ -4,54 +4,60 @@  #include <fluidsynth.h> -struct fluidsynth_userdata { +static struct fluidsynth_userdata {  	fluid_settings_t *settings;  	fluid_synth_t *synth; -	fluid_player_t *player;  	int soundfont; -}; +} *fs;  static void fluidsynth_callback(void *userdata, unsigned char *stream, int const length) { -	struct fluidsynth_userdata *fs = userdata; +	//fluid_player_t *player = userdata; +	(void) userdata;  	fluid_synth_write_float(fs->synth, length / sizeof (float) / 2, stream, 0, 2, stream, 1, 2);  }  static void fluidsynth_free(void *ptr) { -	struct fluidsynth_userdata *fs = ptr; -	fluid_player_stop(fs->player); -	delete_fluid_player(fs->player); -	fluid_player_play(fs->player); +	fluid_player_t *player = ptr; +	fluid_player_stop(player); +	delete_fluid_player(player); +	//fluid_synth_all_notes_off(fs->synth, -1); +	fluid_synth_system_reset(fs->synth); +	#if 0  	fluid_synth_sfunload(fs->synth, fs->soundfont, 0);  	delete_fluid_synth(fs->synth);  	delete_fluid_settings(fs->settings);  	free(fs); +	#endif  }  int module_fluidsynth(struct blob *file, struct userdata *userdata) { -	struct fluidsynth_userdata *fs = malloc(sizeof (struct fluidsynth_userdata));  	if (fs == NULL) { -		return 1; -	} -	fs->settings = new_fluid_settings(); // wasteful, but when trying to 'fix' it it just caused more errors -	fluid_settings_setnum(fs->settings, "synth.gain", 0.5); -	fluid_settings_setint(fs->settings, "player.reset-synth", 0); -	fluid_settings_setnum(fs->settings, "synth.sample-rate", SAMPLE_RATE); -	fs->synth = new_fluid_synth(fs->settings); -	char *soundfont_path = getenv("SOUNDFONT"); -	if (soundfont_path == NULL) { -		soundfont_path = "/usr/share/sounds/sf2/default-GM.sf2"; +		fs = malloc(sizeof (struct fluidsynth_userdata)); +		if (fs == NULL) { +			return 1; +		} +		fs->settings = new_fluid_settings(); // wasteful, but when trying to 'fix' it it just caused more errors +		fluid_settings_setnum(fs->settings, "synth.gain", 0.5); +		fluid_settings_setint(fs->settings, "player.reset-synth", 0); +		fluid_settings_setnum(fs->settings, "synth.sample-rate", SAMPLE_RATE); +		fs->synth = new_fluid_synth(fs->settings); +		char *soundfont_path = getenv("SOUNDFONT"); +		if (soundfont_path == NULL) { +			soundfont_path = "/usr/share/sounds/sf2/default-GM.sf2"; +		} +		fs->soundfont = fluid_synth_sfload(fs->synth, soundfont_path, 1); // ugly hack  	} -	fs->soundfont = fluid_synth_sfload(fs->synth, soundfont_path, 1); // ugly hack -	fs->player = new_fluid_player(fs->synth); -	fluid_player_set_loop(fs->player, -1); -	if (fluid_player_add_mem(fs->player, file->data, file->size) == FLUID_FAILED) { +	fluid_player_t *player = new_fluid_player(fs->synth); +	fluid_player_set_loop(player, -1); +	if (fluid_player_add_mem(player, file->data, file->size) == FLUID_FAILED) {  		fluidsynth_free(fs); +		fs = NULL;  		return 1;  	} -	fluid_player_play(fs->player); +	fluid_player_play(player);  	userdata->callback = fluidsynth_callback; -	userdata->user = fs; +	userdata->user = player;  	userdata->freefunc = fluidsynth_free;  	return 0;  } diff --git a/src/modules/organya.bin b/src/modules/organya.bin Binary files differnew file mode 100644 index 0000000..3f68ac6 --- /dev/null +++ b/src/modules/organya.bin diff --git a/src/modules/organya.c b/src/modules/organya.c new file mode 100644 index 0000000..d214ed2 --- /dev/null +++ b/src/modules/organya.c @@ -0,0 +1,436 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include <stdio.h> + +#include "../include.h" + +// assumes little endian architechture, sorry +// my C port of the 250 SLoC of JS organya player https://github.com/alula/organya-js + +struct song { +	uint16_t wait; +	uint8_t meas[2]; +	int32_t start; +	int32_t end; +	struct instrument { +		uint16_t freq; +		uint8_t wave; +		uint8_t pipi; +		uint16_t notes; +	} instruments[16]; +	struct track { +		int32_t pos; +		uint8_t key; +		uint8_t len; +		uint8_t vol; +		uint8_t pan; +	} *tracks[16]; +}; + +struct organya { +	struct song *song; +	float t; +	int playPos; +	int samplesPerTick; +	int samplesThisTick; +	struct state { +		float t; // may be float? +		int key; +		int frequency; +		int octave; +		float pan; +		float vol; +		int length; +		int num_loops; +		bool playing; +		bool looping; +	} state[16]; +}; + +void organya_synth(struct organya *self, float *const buf, size_t bufsize); + +static void organya_callback(void *userdata, unsigned char *stream, int const length) { +	struct organya *user = userdata; +	organya_synth(user, (float *) stream, length / sizeof (float) / 2); +} + +static signed char const waveTable[] = { +#embed "organya.bin" +}; +static struct drums { +	int filePos; +	int bits; +	int channels; +	int samples; +} *drums = NULL; + +void freesong(struct song *song) { +	for (int i = 0; i < 16; i++) { +		free(song->tracks[i]); +	} +	free(song); +} + +void freefunc(void *ptr) { +	struct organya *self = ptr; +	freesong(self->song); +	free(self); +} + +struct song *song_constructor(struct blob *file) { +	char const *p = file->data; +	size_t bytes = file->size; +	 +	// header check +	if (bytes < 6) { +		return NULL; +	} +	bytes -= 6; +	uint32_t const org1 = *(uint32_t *) p; p += sizeof (uint32_t); +	uint16_t const orgVersion = *(uint16_t *) p; p += sizeof (uint16_t); +	if (org1 != 0x2d67724f || orgVersion != 0x3230) { +		return NULL; +	} +	 +	struct song *self = malloc(sizeof (struct song)); +	if (self == NULL) { +		return NULL; +	} +	 +	if (bytes < 12) { +		return NULL; +	} +	bytes -= 12; +	self->wait = *(uint16_t *) p; p += sizeof (uint16_t); +	self->meas[0] = *(uint8_t *) p; p += sizeof (uint8_t); +	self->meas[1] = *(uint8_t *) p; p += sizeof (uint8_t); +	self->start = *(int32_t *) p; p += sizeof (int32_t); +	self->end = *(int32_t *) p; p += sizeof (int32_t); +	 +	if (bytes < 6 * 16) { +		return NULL; +	} +	bytes -= 6 * 16; +	for (int i = 0; i < 16; i++) { +		self->instruments[i].freq = *(uint16_t *) p; p+= sizeof (uint16_t); +		self->instruments[i].wave = *(uint8_t *) p; p += sizeof (uint8_t); +		self->instruments[i].pipi = *(uint8_t *) p; p += sizeof (uint8_t); +		self->instruments[i].notes = *(uint16_t *) p; p += sizeof (uint16_t); +	} +	 +	for (int i = 0; i < 16; i++) { +		self->tracks[i] = NULL; +	} +	 +	for (int i = 0; i < 16; i++) { +		size_t const length = self->instruments[i].notes; +		struct track *track = malloc(sizeof (track) * length); +		if (track == NULL && length != 0) { +			freesong(self); +			return NULL; +		} +		 +		if (bytes < 8 * length) { +			freesong(self); +			return NULL; +		} +		bytes -= 8 * length; + +		for (unsigned j = 0; j < length; j++) { +			track[j].pos = *(int32_t *) p; p += sizeof (int32_t); +		} +		 +		for (unsigned j = 0; j < length; j++) { +			track[j].key = *(uint8_t *) p; p += sizeof (uint8_t); +		} +		 +		for (unsigned j = 0; j < length; j++) { +			track[j].len = *(uint8_t *) p; p += sizeof (uint8_t); +		} +		 +		for (unsigned j = 0; j < length; j++) { +			track[j].vol = *(uint8_t *) p; p += sizeof (uint8_t); +		} +		 +		for (unsigned j = 0; j < length; j++) { +			track[j].pan = *(uint8_t *) p; p += sizeof (uint8_t); +		} +		 +		self->tracks[i] = track; +	} +	//p == ((char *) file->data) + file->size +	return self; +} + +static int const freqTable[] = {261, 278, 294, 311, 329, 349, 371, 391, 414, 440, 466, 494}; +static int const panTable[] = {0, 43, 86, 129, 172, 215, 256, 297, 340, 383, 426, 469, 512}; +static int const advTable[] = {1, 1, 2, 2, 4, 8, 16, 32}; +static int const octTable[] = {32, 64, 64, 128, 128, 128, 128, 128}; + +int window_initOrganya(void); + +int module_organya(struct blob *file, struct userdata *user) { +	if (drums == NULL) { +		if (window_initOrganya()) { +			return 1; +		} +	} +	 +	struct organya *self = malloc(sizeof (struct organya)); +	if (self == NULL) { +		return 1; +	} +	self->song = song_constructor(file); +	if (self->song == NULL) { +		free(self); +		return 1; +	} +	 +	self->t = 0; +	self->playPos = 0; +	self->samplesPerTick = (SAMPLE_RATE / 1000.0) * self->song->wait; +	self->samplesThisTick = 0; +	for (int i = 0; i < 16; i++) { +		self->state[i] = (struct state) { +			.t = 0, +			.key = 0, +			.frequency = 0, +			.octave = 0, +			.pan = 0.0, +			.vol = 1.0, +			.length = 0, +			.num_loops = 0, +			.playing = false, +			.looping = false, +		}; +	} +	 +	user->user = self; +	user->callback = organya_callback; +	user->freefunc = freefunc; +	 +	return 0; +} + +void organya_update(struct organya *self); + +void organya_synth(struct organya *self, float *const buf, size_t bufsize) { +	float *const leftBuffer = buf + 0; +	float *const rightBuffer = buf + 1; +	for (int sample = 0; sample < bufsize * 2; sample += 2) { +		if (self->samplesThisTick == 0) { +			organya_update(self); +		} +		 +		leftBuffer[sample] = 0; +		rightBuffer[sample] = 0; +		for (int i = 0; i < 16; i++) { +			if (self->state[i].playing) { +				int const samples = (i < 8)? 256: drums[i - 8].samples; +				 +				self->state[i].t += ((float) self->state[i].frequency / (float) SAMPLE_RATE) * advTable[self->state[i].octave]; +				 +				if (((int) self->state[i].t) >= samples) { +					if (self->state[i].looping && self->state[i].num_loops != 1) { +						self->state[i].t = fmod(self->state[i].t, samples); +						if (self->state[i].num_loops != 1) { +							self->state[i].num_loops -= 1; +						} +					} else { +						self->state[i].t = 0; +						self->state[i].playing = false; +						continue; +					} +				} +				 +				int const t = (int) self->state[i].t & ~(advTable[self->state[i].octave] - 1); +				int pos = t % samples; +				int pos2 = !self->state[i].looping && t == samples? +					pos: +					((int) (self->state[i].t + advTable[self->state[i].octave]) & ~(advTable[self->state[i].octave] - 1)) % samples; +				float const s1 = i < 8? +					(waveTable[256 * self->song->instruments[i].wave + pos] / 256.0): +					(((waveTable[drums[i - 8].filePos + pos] & 0xff) - 0x80) / 256.0); +				float const s2 = i < 8? +					(waveTable[256 * self->song->instruments[i].wave + pos2] / 256.0): +					(((waveTable[drums[i - 8].filePos + pos2] & 0xff) - 0x80) / 256.0); +				float const fract = (float) (self->state[i].t - pos) / (float) advTable[self->state[i].octave]; +				 +				// lerp +				float s = s1 + (s2 - s1) * fract; +				 +				s *= pow(10, (float) ((self->state[i].vol - 255) * 8) / 2000.0); +				 +				float const pan = (panTable[(int) self->state[i].pan] - 256) * 10; +				float left = 1, right = 1; + +				if (pan < 0) { +					right = pow(10, (float) (pan / 2000.0)); +				} else if (pan > 0) { +					left = pow(10, (float) (-pan / 2000.0)); +				} +				 +				leftBuffer[sample] += s * left; +				rightBuffer[sample] += s * right; +			} +		} + +		if (++self->samplesThisTick == self->samplesPerTick) { +			self->playPos += 1; +			self->samplesThisTick = 0; +			 +			if (self->playPos == self->song->end) { +				self->playPos = self->song->start; +			} +		} +	} +} + +struct track *find(struct track *array, int value, size_t members) { +	for (int i = 0; i < members; i++) { +		if (array[i].pos == value) { +			return array + i; +		} +	} +	return NULL; +} + +void organya_update(struct organya *self) { +	for (int track = 0; track < 8; track++) { +		size_t const length = self->song->instruments[track].notes; +		struct track *const note = find(self->song->tracks[track], self->playPos, length); +		if (note) { +			if (note->key != 255) { +				int const octave = note->key / 12; +				int const key = note->key % 12; +				 +				if (self->state[track].key == 255) { +					self->state[track].key = note->key; +					 +					self->state[track].frequency = freqTable[key] * octTable[octave] + (self->song->instruments[track].freq - 1000); +					 +					if (self->song->instruments[track].pipi != 0 && !self->state[track].playing) { +						self->state[track].num_loops = ((octave + 1) * 4); +					} +				} else if (self->state[track].key != note->key) { +					self->state[track].key = note->key; +					self->state[track].frequency = freqTable[key] * octTable[octave] + (self->song->instruments[track].freq - 1000); +				} +				 +				if (self->song->instruments[track].pipi != 0 && !self->state[track].playing) { +					self->state[track].num_loops = ((octave + 1) * 4); +				} +				 +				self->state[track].octave = octave; +				self->state[track].playing = true; +				self->state[track].looping = true; +				self->state[track].length = note->len; +			} + +			if (self->state[track].key != 255) { +				if (note->vol != 255) { +					self->state[track].vol = note->vol; +				} +				if (note->pan != 255) { +					self->state[track].pan = note->pan; +				} +			} +		} + +		if (self->state[track].length == 0) { +			if (self->state[track].key != 255) { +				if (self->song->instruments[track].pipi == 0) { +					self->state[track].looping = false; +				} +				 +				self->state[track].playing = false; +				self->state[track].key = 255; +			} +		} else { +			self->state[track].length--; +		} +	} +	 +	for (int track = 8; track < 16; track++) { +		size_t const length = self->song->instruments[track].notes; +		struct track *const note = find(self->song->tracks[track], self->playPos, length); +		if (!note) { +			continue; +		} +		 +		if (note->key != 255) { +			self->state[track].frequency = note->key * 800 + 100; +			self->state[track].t = 0; +			self->state[track].playing = true; +		} +		 +		if (note->vol != 255) { +			self->state[track].vol = note->vol; +		} +		if (note->pan != 255) { +			self->state[track].pan = note->pan; +		} +	} +} + +int window_initOrganya(void) { +	size_t drumsLen = 0; +	 +	for (size_t i = 256 * 100; i < sizeof (waveTable) - 4; i++) { +		signed char const *const p = waveTable; +		if (*(uint32_t *) (p + i) == 0x45564157) { +			i += sizeof (uint32_t); +			uint32_t const riffId = *(uint32_t *) (p + i); i += sizeof (uint32_t); +			uint32_t const riffLen = *(uint32_t *) (p + i); i += sizeof (uint32_t); +			if (riffId != 0x20746d66) { +				fputs("invalid RIFF chunk id", stderr); +				continue; +			} +			 +			size_t const startPos = i; +			int const aFormat = *(uint16_t *) (p + i); i += sizeof (uint16_t); +			if (aFormat != 1) { +				fputs("invalid audio format", stderr); +				i = startPos + riffLen; +				continue; +			} +			 +			int const channels = *(uint16_t *) (p + i); i += sizeof (uint16_t); +			if (channels != 1) { +				fputs("only 1 channel files are supported", stderr); +				i = startPos + riffLen; +				continue; +			} +			 +			int const samples = *(uint32_t *) (p + i); i += sizeof (uint32_t) + 6; // skip rate + padding +			int const bits = *(uint16_t *) (p + i); i += sizeof (uint16_t); +			int const wavData = *(uint32_t *) (p + i); i += sizeof (uint32_t); +			int const wavLen = *(uint32_t *) (p + i); i += sizeof (uint32_t); +			 +			if (wavData != 0x61746164) { +				i = startPos + riffLen; +				continue; +			} +			 +			void *newDrums = realloc(drums, sizeof (struct drums) * (drumsLen + 1)); +			if (newDrums == NULL) { +				free(drums); +				drums = NULL; +				return 1; +			} +			drums = newDrums; +			drums[drumsLen].filePos = i; +			drums[drumsLen].bits = bits; +			drums[drumsLen].channels = channels; +			drums[drumsLen].samples = wavLen; +			drumsLen++; +			 +			i += wavLen; +		} +	} +	return 0; +} diff --git a/src/portaudio.c b/src/portaudio.c index e8e9839..e5c5d92 100644 --- a/src/portaudio.c +++ b/src/portaudio.c @@ -36,6 +36,22 @@ int audio_callback(void const *inBuf, void *outBuf, unsigned long const frameCou  int (*file_ext(char *file))(struct blob *, struct userdata *); +#include <alsa/asoundlib.h> +#include <stdarg.h> +static void alsa_shut_up(char const *file, int line, char const *function, int err, char const *fmt, ...) { +	if (err == 0) { +		// duckGPT claims its not unsafe to return before using va_ functions +		return; +	} +	// ALSA lib (real) +	fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function); +	va_list list; +	va_start(list, fmt); +	vfprintf(stderr, fmt, list); +	va_end(list); +	putc('\n', stderr); +} +  int main(int argc, char **argv) {  	if (argc != 2) {  		eprintf("usage: %s audio-file.{mid,mod,xm,it,s3m}\n", argv[0]); @@ -51,16 +67,21 @@ int main(int argc, char **argv) {  			perror(argv[1]);  			return EXIT_FAILURE;  		} -		module_func(&file, &userdata); +		if (module_func(&file, &userdata)) { +			eprintf("%s: failed to load\n", argv[1]); +			return EXIT_FAILURE; +		}  	}  	signal(SIGINT, sigint_handler); // signal(3) claims this method is deprecated, but.. +	snd_lib_error_set_handler(alsa_shut_up);  	PaError paErr;  	if ((paErr = Pa_Initialize()) != paNoError) {  		printf("error: Pa_Initialize: %s\n", Pa_GetErrorText(paErr));  		goto error;  	} +	snd_lib_error_set_handler(NULL);  	PaStream *stream;  	if ((paErr = Pa_OpenDefaultStream(  | 
