diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | examples/sandbox.lua | 30 | ||||
| -rw-r--r-- | interface.c | 14 | ||||
| -rw-r--r-- | interface.h | 7 | ||||
| -rw-r--r-- | main.c | 29 | ||||
| -rw-r--r-- | midi.c | 166 | ||||
| -rw-r--r-- | mutex.h | 6 | ||||
| -rw-r--r-- | synth.h | 4 | ||||
| -rw-r--r-- | version.h | 16 |
9 files changed, 169 insertions, 105 deletions
| @@ -1,7 +1,7 @@ | |||
| 1 | CC := cc | 1 | CC := cc |
| 2 | CFLAGS := -Wall -Wextra -Wshadow -Wunused -Wswitch-enum -Wpedantic -ggdb | 2 | CFLAGS := -Wall -Wextra -Wshadow -Wunused -Wswitch-enum -Wpedantic -ggdb |
| 3 | LDFLAGS := -lm -ldl -lpthread -lasound | 3 | LDFLAGS := -lm -ldl -lpthread -lasound |
| 4 | FILES := main.c midi.c synth.c mutex.c minisdl_audio.c | 4 | FILES := main.c midi.c synth.c interface.c mutex.c minisdl_audio.c |
| 5 | PROG := ttdaw | 5 | PROG := ttdaw |
| 6 | 6 | ||
| 7 | $(PROG): main.c | 7 | $(PROG): main.c |
diff --git a/examples/sandbox.lua b/examples/sandbox.lua new file mode 100644 index 0000000..43c3ec7 --- /dev/null +++ b/examples/sandbox.lua | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | -- Song file structure | ||
| 2 | -- This is just an outline of it and this will change. | ||
| 3 | |||
| 4 | -- API specification: | ||
| 5 | -- note_on(note, sf_preset, effect) | ||
| 6 | -- note_off(note) | ||
| 7 | -- delay(millisecons) | ||
| 8 | -- play_pattern(pattern_name, repeat) | ||
| 9 | |||
| 10 | -- Effects: | ||
| 11 | -- delay | ||
| 12 | -- reverb | ||
| 13 | |||
| 14 | -- This is a pattern definition. | ||
| 15 | -- Columns go from A..F and rows go from 1..4. | ||
| 16 | -- This blocks will be visualized in the DAW. | ||
| 17 | pattern("A1", function(self) | ||
| 18 | for i = 1, 10 do | ||
| 19 | note_on(40 + i, 1, nil) | ||
| 20 | delay(10) | ||
| 21 | end | ||
| 22 | end) | ||
| 23 | |||
| 24 | -- This is the actual song timeline. | ||
| 25 | timeline(function(self) | ||
| 26 | play_pattern("A1", 1) | ||
| 27 | play_pattern("F2", 3) | ||
| 28 | play_pattern("D4", 2) | ||
| 29 | end) | ||
| 30 | |||
diff --git a/interface.c b/interface.c index 6399cb8..26aca55 100644 --- a/interface.c +++ b/interface.c | |||
| @@ -1,2 +1,16 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <unistd.h> | ||
| 3 | |||
| 1 | #include "interface.h" | 4 | #include "interface.h" |
| 5 | #include "mutex.h" | ||
| 6 | |||
| 7 | void *interface(void *arg) { | ||
| 8 | InterfaceArgs* args = (InterfaceArgs*)arg; | ||
| 9 | (void)args; | ||
| 2 | 10 | ||
| 11 | while(1) { | ||
| 12 | // Do the thread stuff here. | ||
| 13 | sleep(1); | ||
| 14 | fprintf(stdout, "hi from interface thread\n"); | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/interface.h b/interface.h index e320785..da8227e 100644 --- a/interface.h +++ b/interface.h | |||
| @@ -1,5 +1,12 @@ | |||
| 1 | #ifndef INTERFACE_H | 1 | #ifndef INTERFACE_H |
| 2 | #define INTERFACE_H | 2 | #define INTERFACE_H |
| 3 | 3 | ||
| 4 | typedef struct { | ||
| 5 | char *soundfont_file; | ||
| 6 | int soundfont_preset; | ||
| 7 | } InterfaceArgs; | ||
| 8 | |||
| 9 | void *interface(void *arg); | ||
| 10 | |||
| 4 | #endif // INTERFACE_H | 11 | #endif // INTERFACE_H |
| 5 | 12 | ||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include "version.h" | 7 | #include "version.h" |
| 8 | #include "midi.h" | 8 | #include "midi.h" |
| 9 | #include "synth.h" | 9 | #include "synth.h" |
| 10 | #include "interface.h" | ||
| 10 | #include "mutex.h" | 11 | #include "mutex.h" |
| 11 | 12 | ||
| 12 | void help(const char *argv0) { | 13 | void help(const char *argv0) { |
| @@ -27,7 +28,7 @@ int main(int argc, char *argv[]) { | |||
| 27 | { "list", 0, NULL, 'l' }, | 28 | { "list", 0, NULL, 'l' }, |
| 28 | { "client", 1, NULL, 'c' }, | 29 | { "client", 1, NULL, 'c' }, |
| 29 | { "soundfont", 1, NULL, 's' }, | 30 | { "soundfont", 1, NULL, 's' }, |
| 30 | { "preset", 1, NULL, 'p' }, | 31 | { "preset", 0, NULL, 'p' }, |
| 31 | { "help", 0, NULL, 'h' }, | 32 | { "help", 0, NULL, 'h' }, |
| 32 | { "version", 0, NULL, 'v' }, | 33 | { "version", 0, NULL, 'v' }, |
| 33 | { 0 }, | 34 | { 0 }, |
| @@ -35,7 +36,7 @@ int main(int argc, char *argv[]) { | |||
| 35 | 36 | ||
| 36 | char *port_name = NULL; | 37 | char *port_name = NULL; |
| 37 | char *soundfont_file = NULL; | 38 | char *soundfont_file = NULL; |
| 38 | int soundfont_preset = -1; | 39 | int soundfont_preset = 0; |
| 39 | 40 | ||
| 40 | int opt; | 41 | int opt; |
| 41 | while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { | 42 | while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { |
| @@ -56,11 +57,11 @@ int main(int argc, char *argv[]) { | |||
| 56 | help(argv[0]); | 57 | help(argv[0]); |
| 57 | return 0; | 58 | return 0; |
| 58 | case 'v': | 59 | case 'v': |
| 59 | fprintf(stdout, "ttdaw version %s\n", TTDAW_VERSION); | 60 | fprintf(stdout, "ttdaw version %s\n", VERSION); |
| 60 | fprintf(stdout, "Website: %s.\n", TTDAW_WEBSITE); | 61 | fprintf(stdout, "Website: %s.\n", WEBSITE); |
| 61 | fprintf(stdout, "%s\n", TTDAW_LICENSE); | 62 | fprintf(stdout, "%s\n", LICENSE); |
| 62 | fprintf(stdout, "%s\n", TTDAW_WARRANTY); | 63 | fprintf(stdout, "%s\n", WARRANTY); |
| 63 | fprintf(stdout, "\n%s\n", TTDAW_AUTHOR); | 64 | fprintf(stdout, "\n%s\n", AUTHOR); |
| 64 | return 0; | 65 | return 0; |
| 65 | default: | 66 | default: |
| 66 | fprintf(stdout, "Missing options. Check help.\n"); | 67 | fprintf(stdout, "Missing options. Check help.\n"); |
| @@ -102,8 +103,22 @@ int main(int argc, char *argv[]) { | |||
| 102 | return 1; | 103 | return 1; |
| 103 | } | 104 | } |
| 104 | 105 | ||
| 106 | // Create UI thread. | ||
| 107 | pthread_t interface_thread; | ||
| 108 | InterfaceArgs interface_args = { | ||
| 109 | .soundfont_file = soundfont_file, | ||
| 110 | .soundfont_preset = soundfont_preset, | ||
| 111 | }; | ||
| 112 | |||
| 113 | if (pthread_create(&interface_thread, NULL, interface, (void*)&interface_args) != 0) { | ||
| 114 | fprintf(stderr, "Error creating interface thread\n"); | ||
| 115 | return 1; | ||
| 116 | } | ||
| 117 | |||
| 118 | // Start threads. | ||
| 105 | pthread_join(midi_thread, NULL); | 119 | pthread_join(midi_thread, NULL); |
| 106 | pthread_join(synth_thread, NULL); | 120 | pthread_join(synth_thread, NULL); |
| 121 | pthread_join(interface_thread, NULL); | ||
| 107 | 122 | ||
| 108 | // Destroy mutex. | 123 | // Destroy mutex. |
| 109 | destroy_mutex(); | 124 | destroy_mutex(); |
| @@ -13,110 +13,108 @@ static snd_seq_addr_t *ports; | |||
| 13 | void *midi(void *arg) { | 13 | void *midi(void *arg) { |
| 14 | MidiArgs* args = (MidiArgs*)arg; | 14 | MidiArgs* args = (MidiArgs*)arg; |
| 15 | 15 | ||
| 16 | while (1) { | 16 | if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) { |
| 17 | if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) { | 17 | fprintf(stderr, "Error opening ALSA sequencer.\n"); |
| 18 | fprintf(stderr, "Error opening ALSA sequencer.\n"); | 18 | exit(1); |
| 19 | exit(1); | 19 | } |
| 20 | } | ||
| 21 | |||
| 22 | if (snd_seq_set_client_name(seq_handle, CLIENT_NAME) < 0) { | ||
| 23 | fprintf(stderr, "Could not set up client name.\n"); | ||
| 24 | exit(1); | ||
| 25 | } | ||
| 26 | 20 | ||
| 27 | if (snd_seq_create_simple_port(seq_handle, CLIENT_NAME, | 21 | if (snd_seq_set_client_name(seq_handle, CLIENT_NAME) < 0) { |
| 28 | SND_SEQ_PORT_CAP_WRITE | | 22 | fprintf(stderr, "Could not set up client name.\n"); |
| 29 | SND_SEQ_PORT_CAP_SUBS_WRITE, | 23 | exit(1); |
| 30 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | | 24 | } |
| 31 | SND_SEQ_PORT_TYPE_APPLICATION) < 0) { | ||
| 32 | fprintf(stderr, "Error creating sequencer port.\n"); | ||
| 33 | exit(1); | ||
| 34 | } | ||
| 35 | 25 | ||
| 36 | // Connecting ports. | 26 | if (snd_seq_create_simple_port(seq_handle, CLIENT_NAME, |
| 37 | ports = realloc(ports, MAX_MIDI_PORTS * sizeof(snd_seq_addr_t)); | 27 | SND_SEQ_PORT_CAP_WRITE | |
| 38 | if (snd_seq_parse_address(seq_handle, &ports[0], args->port_name) < 0) { | 28 | SND_SEQ_PORT_CAP_SUBS_WRITE, |
| 39 | fprintf(stderr, "Invalid port %s.\n", args->port_name); | 29 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | |
| 40 | exit(1); | 30 | SND_SEQ_PORT_TYPE_APPLICATION) < 0) { |
| 41 | } | 31 | fprintf(stderr, "Error creating sequencer port.\n"); |
| 32 | exit(1); | ||
| 33 | } | ||
| 42 | 34 | ||
| 43 | // Listing assigned ports. | 35 | // Connecting ports. |
| 44 | fprintf(stdout, "Ports:\n"); | 36 | ports = realloc(ports, MAX_MIDI_PORTS * sizeof(snd_seq_addr_t)); |
| 45 | for (int j = 0; j < MAX_MIDI_PORTS; j++) { | 37 | if (snd_seq_parse_address(seq_handle, &ports[0], args->port_name) < 0) { |
| 46 | fprintf(stdout, " client: %d, port: %d\n", ports[j].client, ports[j].port); | 38 | fprintf(stderr, "Invalid port %s.\n", args->port_name); |
| 47 | } | 39 | exit(1); |
| 40 | } | ||
| 48 | 41 | ||
| 49 | // Connecting ports. | 42 | // Listing assigned ports. |
| 50 | for (int i = 0; i < MAX_MIDI_PORTS; ++i) { | 43 | fprintf(stdout, "Ports:\n"); |
| 51 | int err = snd_seq_connect_from(seq_handle, 0, ports[i].client, ports[i].port); | 44 | for (int j = 0; j < MAX_MIDI_PORTS; j++) { |
| 52 | if (err < 0) { | 45 | fprintf(stdout, " client: %d, port: %d\n", ports[j].client, ports[j].port); |
| 53 | fprintf(stderr, "Cannot connect from port %d:%d - %s", ports[i].client, ports[i].port, snd_strerror(err)); | 46 | } |
| 54 | exit(1); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | 47 | ||
| 58 | if (snd_seq_nonblock(seq_handle, 1) < 0) { | 48 | // Connecting ports. |
| 59 | fprintf(stderr, "Set nonblock mode failed."); | 49 | for (int i = 0; i < MAX_MIDI_PORTS; ++i) { |
| 50 | int err = snd_seq_connect_from(seq_handle, 0, ports[i].client, ports[i].port); | ||
| 51 | if (err < 0) { | ||
| 52 | fprintf(stderr, "Cannot connect from port %d:%d - %s", ports[i].client, ports[i].port, snd_strerror(err)); | ||
| 60 | exit(1); | 53 | exit(1); |
| 61 | } | 54 | } |
| 55 | } | ||
| 56 | |||
| 57 | if (snd_seq_nonblock(seq_handle, 1) < 0) { | ||
| 58 | fprintf(stderr, "Set nonblock mode failed."); | ||
| 59 | exit(1); | ||
| 60 | } | ||
| 61 | |||
| 62 | 62 | ||
| 63 | // Reading MIDI device. | ||
| 64 | struct pollfd *pfds; | ||
| 65 | size_t npfds; | ||
| 63 | 66 | ||
| 64 | // Reading MIDI device. | 67 | npfds = snd_seq_poll_descriptors_count(seq_handle, POLLIN); |
| 65 | struct pollfd *pfds; | 68 | pfds = alloca(sizeof(*pfds) * npfds); |
| 66 | size_t npfds; | ||
| 67 | 69 | ||
| 68 | npfds = snd_seq_poll_descriptors_count(seq_handle, POLLIN); | 70 | for (;;) { |
| 69 | pfds = alloca(sizeof(*pfds) * npfds); | 71 | snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLIN); |
| 72 | if (poll(pfds, npfds, -1) < 0) { | ||
| 73 | break; | ||
| 74 | } | ||
| 70 | 75 | ||
| 71 | for (;;) { | 76 | for (;;) { |
| 72 | snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLIN); | 77 | snd_seq_event_t *ev; |
| 73 | if (poll(pfds, npfds, -1) < 0) { | 78 | |
| 79 | if (snd_seq_event_input(seq_handle, &ev) < 0) { | ||
| 74 | break; | 80 | break; |
| 75 | } | 81 | } |
| 76 | 82 | ||
| 77 | for (;;) { | 83 | if (ev) { |
| 78 | snd_seq_event_t *ev; | 84 | pthread_mutex_lock(&mutex); |
| 85 | switch (ev->type) { | ||
| 86 | case SND_SEQ_EVENT_NOTEON: | ||
| 87 | shared_data.note = ev->data.note.note; | ||
| 88 | shared_data.state = 1; | ||
| 89 | shared_data.velocity = ev->data.note.velocity; | ||
| 90 | printf("%3d:%-3dNote on %2d, note %d, velocity: %3d\n", | ||
| 91 | ev->source.client, ev->source.port, | ||
| 92 | ev->data.note.channel, | ||
| 93 | ev->data.note.note, | ||
| 94 | ev->data.note.velocity); | ||
| 95 | break; | ||
| 96 | |||
| 97 | case SND_SEQ_EVENT_NOTEOFF: | ||
| 98 | shared_data.note = ev->data.note.note; | ||
| 99 | shared_data.state = 0; | ||
| 100 | shared_data.velocity = 0; | ||
| 101 | printf("%3d:%-3dNote off\t%2d, note %d\n", | ||
| 102 | ev->source.client, ev->source.port, | ||
| 103 | ev->data.note.channel, | ||
| 104 | ev->data.note.note); | ||
| 105 | break; | ||
| 79 | 106 | ||
| 80 | if (snd_seq_event_input(seq_handle, &ev) < 0) { | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | 107 | ||
| 84 | if (ev) { | ||
| 85 | pthread_mutex_lock(&mutex); | ||
| 86 | switch (ev->type) { | ||
| 87 | case SND_SEQ_EVENT_NOTEON: | ||
| 88 | shared_data.note = ev->data.note.note; | ||
| 89 | shared_data.state = 1; | ||
| 90 | shared_data.velocity = ev->data.note.velocity; | ||
| 91 | printf("%3d:%-3dNote on %2d, note %d, velocity: %3d\n", | ||
| 92 | ev->source.client, ev->source.port, | ||
| 93 | ev->data.note.channel, | ||
| 94 | ev->data.note.note, | ||
| 95 | ev->data.note.velocity); | ||
| 96 | break; | ||
| 97 | |||
| 98 | case SND_SEQ_EVENT_NOTEOFF: | ||
| 99 | shared_data.note = ev->data.note.note; | ||
| 100 | shared_data.state = 0; | ||
| 101 | shared_data.velocity = 0; | ||
| 102 | printf("%3d:%-3dNote off\t%2d, note %d\n", | ||
| 103 | ev->source.client, ev->source.port, | ||
| 104 | ev->data.note.channel, | ||
| 105 | ev->data.note.note); | ||
| 106 | break; | ||
| 107 | default: | ||
| 108 | break; | ||
| 109 | } | ||
| 110 | |||
| 111 | shared_data.action = 1; | ||
| 112 | |||
| 113 | pthread_cond_signal(&cond_synth); | ||
| 114 | pthread_mutex_unlock(&mutex); | ||
| 115 | } | 108 | } |
| 116 | } | ||
| 117 | 109 | ||
| 118 | fflush(stdout); | 110 | shared_data.action = 1; |
| 111 | |||
| 112 | pthread_cond_signal(&cond_synth); | ||
| 113 | pthread_mutex_unlock(&mutex); | ||
| 114 | } | ||
| 119 | } | 115 | } |
| 116 | |||
| 117 | fflush(stdout); | ||
| 120 | } | 118 | } |
| 121 | } | 119 | } |
| 122 | 120 | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | #ifndef MUTEX_H | 1 | #ifndef MUTEX_H_ |
| 2 | #define MUTEX_H | 2 | #define MUTEX_H_ |
| 3 | 3 | ||
| 4 | #include <pthread.h> | 4 | #include <pthread.h> |
| 5 | 5 | ||
| @@ -19,5 +19,5 @@ extern pthread_cond_t cond_synth; | |||
| 19 | void initialize_mutex(); | 19 | void initialize_mutex(); |
| 20 | void destroy_mutex(); | 20 | void destroy_mutex(); |
| 21 | 21 | ||
| 22 | #endif // MUTEX_H | 22 | #endif // MUTEX_H_ |
| 23 | 23 | ||
| @@ -2,7 +2,7 @@ | |||
| 2 | #define SYNTH_H_ | 2 | #define SYNTH_H_ |
| 3 | 3 | ||
| 4 | #define AUDIO_FREQ 44100 | 4 | #define AUDIO_FREQ 44100 |
| 5 | #define AUDIO_SAMPLES 64 | 5 | #define AUDIO_SAMPLES 128 |
| 6 | #define AUDIO_CHANNELS 2 | 6 | #define AUDIO_CHANNELS 2 |
| 7 | 7 | ||
| 8 | typedef struct { | 8 | typedef struct { |
| @@ -12,5 +12,5 @@ typedef struct { | |||
| 12 | 12 | ||
| 13 | void *synth(void *arg); | 13 | void *synth(void *arg); |
| 14 | 14 | ||
| 15 | #endif // SYNTH_H_ | 15 | #endif // SYNTH_H_ |
| 16 | 16 | ||
| @@ -1,11 +1,11 @@ | |||
| 1 | #ifndef TTDAW_VERSION_H | 1 | #ifndef VERSION_H_ |
| 2 | #define TTDAW_VERSION_H | 2 | #define VERSION_H_ |
| 3 | 3 | ||
| 4 | #define TTDAW_VERSION "0.1" | 4 | #define VERSION "0.1" |
| 5 | #define TTDAW_WEBSITE "https://github.com/mitjafelicijan/ttdaw" | 5 | #define WEBSITE "https://github.com/mitjafelicijan/ttdaw" |
| 6 | #define TTDAW_LICENSE "This is free software: you are free to change and redistribute it." | 6 | #define LICENSE "This is free software: you are free to change and redistribute it." |
| 7 | #define TTDAW_WARRANTY "There is NO WARRANTY, to the extent permitted by law." | 7 | #define WARRANTY "There is NO WARRANTY, to the extent permitted by law." |
| 8 | #define TTDAW_AUTHOR "Written by Mitja Felicijan <https://mitjafelicijan.com>." | 8 | #define AUTHOR "Written by Mitja Felicijan <https://mitjafelicijan.com>." |
| 9 | 9 | ||
| 10 | #endif | 10 | #endif // VERSION_H_ |
| 11 | 11 | ||
