1
0
mirror of https://github.com/sjlongland/atinysynth.git synced 2025-09-13 10:03:15 +10:00

- Wav output instead of raw via libao

- Support missing live ao support
- Added support for script file
This commit is contained in:
Luciano Martorella 2021-04-25 15:43:45 +02:00
parent a83da25fa1
commit 6e5978d7a3
4 changed files with 70 additions and 30 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
sine.c
obj
bin
out.wav

View File

@ -16,7 +16,7 @@ Principle of operation
----------------------
The library runs as a state machine. Synthesis is performed completely
using only integer artihmatic operations: specifically addition,
using only integer arithmetic operations: specifically addition,
subtraction, left and right shifts, and occasional multiplication. This
makes it suitable for smaller CPU cores such as Atmel's TinyAVR, ARM's
Cortex M0+, lower-end TI MSP430 and other minimalist CPU cores that lack
@ -51,7 +51,7 @@ waveform moves through the following states:
state of multiple voices to be configured at some convenient point in
the program in bulk whilst still providing flexibility on when a
particular note is played. As there is no amplitude change, an
"infinte" time delay may also be specified here, allowing a note to
"infinite" time delay may also be specified here, allowing a note to
be configured then triggered "on cue".
2. Attack phase: The amplitude starts at 0, and using an approximated
@ -287,7 +287,7 @@ amplitude.
### PC port (`pc`)
This uses `libao` and a command line interface to simulate the output of
the synthesizer. It was used to debug the synthesizer.
the synthesizer and to output a `.wav` file. It was used to debug the synthesizer.
The synthesizer commands are given as command-line arguments:
@ -321,8 +321,11 @@ The synthesizer commands are given as command-line arguments:
`A`
* `reset` resets the ADSR state machine for the selected channel.
Or, alternatively, you can pass all the above commands stored in a text file:
* `-- NAME` loads and run the script, and skip all the remaining arguments.
Once the `enable` bit-mask is set, the program loops, playing sound via
`libao` and writing the samples to `out.raw` for later analysis until
`libao` and writing the samples to `out.wav` for later analysis until
all bits in the `enable` bit-mask are cleared by the ADSR state machines.
When the program runs out of command line arguments, it exits.
When the program runs out of command line arguments, or the script ends, it exits.

View File

@ -28,6 +28,25 @@ const uint16_t synth_freq = 32000;
struct voice_ch_t poly_voice[16];
struct poly_synth_t synth;
/* Read a script instead of command-line tokens */
static void readScript(const char* name, int* argc, char*** argv) {
FILE *fp = fopen(name, "r");
char token[64];
int n = 0;
int size = 16;
char** list = malloc(size * sizeof(char*));
while (fscanf(fp, "%63s", token) == 1) {
list[n++] = strdup(token);
if (n >= size) {
size += 16;
list = realloc(list, size * sizeof(char*));
}
}
*argv = list;
*argc = n;
}
int main(int argc, char** argv) {
int voice = 0;
int16_t samples[8192];
@ -37,36 +56,47 @@ int main(int argc, char** argv) {
synth.enable = 0;
synth.mute = 0;
ao_device* device;
ao_sample_format format;
ao_initialize();
memset(poly_voice, 0, sizeof(poly_voice));
FILE* out = fopen("out.raw", "wb");
{
int driver = ao_default_driver_id();
memset(&format, 0, sizeof(format));
format.bits = 16;
format.channels = 1;
format.rate = synth_freq;
format.byte_format = AO_FMT_NATIVE;
device = ao_open_live(driver, &format, NULL);
if (!device) {
fprintf(stderr, "Failed to open audio device\n");
return 1;
}
ao_sample_format format;
memset(&format, 0, sizeof(format));
format.bits = 16;
format.channels = 1;
format.rate = synth_freq;
format.byte_format = AO_FMT_NATIVE;
ao_initialize();
int wavDriver = ao_driver_id("wav");
ao_device* wavDevice = ao_open_file(wavDriver, "out.wav", 1, &format, NULL);
if (!wavDevice) {
fprintf(stderr, "Failed to open WAV device\n");
return 1;
}
ao_device* liveDevice = NULL;
int liveDriver = ao_default_driver_id();
liveDevice = ao_open_live(liveDriver, &format, NULL);
if (!liveDevice) {
printf("Live driver not available\n");
}
argc--;
argv++;
while (argc > 0) {
int res = 0;
if (!strcmp(argv[0], "end"))
break;
/* Check for external script */
if (!strcmp(argv[0], "--")) {
const char* name = argv[1];
_DPRINTF("reading script %s\n", name);
readScript(name, &argc, &argv);
// Fake item will be skipped at the end of the if
argc++;
argv--;
/* Voice selection */
if (!strcmp(argv[0], "voice")) {
} else if (!strcmp(argv[0], "voice")) {
voice = atoi(argv[1]);
_DPRINTF("select voice %d\n", voice);
argv++;
@ -203,7 +233,7 @@ int main(int argc, char** argv) {
while (synth.enable) {
int16_t* sample_ptr = samples;
uint16_t samples_remain = 8192;
uint16_t samples_remain = sizeof(samples) / sizeof(uint16_t);
/* Fill the buffer as much as we can */
while (synth.enable && samples_remain) {
_DPRINTF("enable = 0x%lx\n", synth.enable);
@ -213,15 +243,18 @@ int main(int argc, char** argv) {
samples_sz++;
samples_remain--;
}
fwrite(samples, samples_sz, 2, out);
ao_play(device, (char*)samples, 2*samples_sz);
ao_play(wavDevice, (char*)samples, 2*samples_sz);
if (liveDevice) {
ao_play(liveDevice, (char*)samples, 2*samples_sz);
}
samples_sz = 0;
}
}
fclose(out);
ao_close(device);
ao_close(wavDevice);
if (liveDevice) {
ao_close(liveDevice);
}
ao_shutdown();
return 0;
}

3
ports/pc/test Normal file
View File

@ -0,0 +1,3 @@
voice 0 scale 100 delay 1 attack 100 decay 100 sustain 1000 release 10 peak 63 samp 32 triangle 2000 63
voice 1 scale 100 delay 1 attack 100 decay 100 sustain 50 release 1000 peak 63 samp 32 sawtooth 1500 63
en 3