Created
September 3, 2017 00:31
-
-
Save MegaLoler/d95c5b8b4bb38f7a042935412fffbb73 to your computer and use it in GitHub Desktop.
A live midi sequencer for the Launchkey Mini!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// by megaloler! ([email protected]) | |
// a custom 100% midi-controlled live loop-based sequencer for the launchkey mini! | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <alsa/asoundlib.h> | |
#define ALSA_CLIENT_NAME "LaunchSEQ" | |
#define MAX_PATTERN_EVENTS 4096 | |
#define MAX_SONG_PATTERNS 16 | |
#define MAX_SONG_SCENES 128 | |
#define MAX_SONG_PATCHES 128 | |
#define MAX_QUEUE_SIZE 8192 | |
#define METRONOME_PULSE_FRAMES_PER_BEAT 16 | |
typedef struct | |
{ | |
int patch; | |
int events_length; | |
snd_seq_event_t events[MAX_PATTERN_EVENTS]; | |
} Pattern; | |
typedef struct | |
{ | |
int scene_pattern_masks[MAX_SONG_SCENES]; | |
Pattern patterns[MAX_SONG_PATTERNS]; | |
int measures_per_pattern; | |
int beats_per_measure; | |
int bpm; | |
int ticks_per_beat; | |
int quantize; | |
} Song; | |
Song *new_song() | |
{ | |
Song *song = malloc(sizeof *song); | |
song->measures_per_pattern = 4; | |
song->beats_per_measure = 4; | |
song->bpm = 125; | |
song->ticks_per_beat = 64; | |
song->quantize = 128; | |
int i; | |
for(i = 0; i < MAX_SONG_SCENES; i++) | |
{ | |
song->scene_pattern_masks[i] = 0; | |
} | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
song->patterns[i].patch = 0; | |
} | |
int a; | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
song->patterns[a].events_length = 0; | |
} | |
return song; | |
} | |
typedef enum | |
{ | |
MODE_SONG, | |
MODE_LOOP, | |
MODE_RECORD | |
} PlayMode; | |
typedef enum | |
{ | |
SCREEN_PATTERN, | |
SCREEN_SONG, | |
SCREEN_PATCH | |
} Screen; | |
int current_scene = 0; | |
int playing = 0; | |
PlayMode current_mode = MODE_LOOP; | |
Screen current_screen = SCREEN_PATTERN; | |
Song *song; | |
int pressed_pads = 0; | |
int first_pressed_pad = 0; | |
void clear_pattern(int pattern) | |
{ | |
song->patterns[pattern].events_length = 0; | |
} | |
void record_event(int pattern, snd_seq_event_t event) | |
{ | |
song->patterns[pattern].events[song->patterns[pattern].events_length++] = event; | |
} | |
int get_mask(int pad) | |
{ | |
return 1 << pad; | |
} | |
void set_pad_pressed_state(int pad, int state) | |
{ | |
int mask = get_mask(pad); | |
if(state) | |
{ | |
pressed_pads |= mask; | |
} | |
else | |
{ | |
pressed_pads &= ~mask; | |
} | |
} | |
void set_pad_pressed(int pad) | |
{ | |
set_pad_pressed_state(pad, 1); | |
} | |
void clear_pad_pressed(int pad) | |
{ | |
set_pad_pressed_state(pad, 0); | |
} | |
int get_pad_pressed(int pad) | |
{ | |
int mask = get_mask(pad); | |
return pressed_pads & mask; | |
} | |
int get_scene_pattern_mask(int scene) | |
{ | |
return song->scene_pattern_masks[scene]; | |
} | |
int get_current_pattern_mask() | |
{ | |
return get_scene_pattern_mask(current_scene); | |
} | |
void set_scene_pattern_mask(int scene, int mask) | |
{ | |
song->scene_pattern_masks[scene] = mask; | |
} | |
void set_current_pattern_mask(int mask) | |
{ | |
set_scene_pattern_mask(current_scene, mask); | |
} | |
void set_pattern_enabled_state(int pattern, int state) | |
{ | |
int mask = get_mask(pattern); | |
if(state) | |
{ | |
set_current_pattern_mask(get_current_pattern_mask() | mask); | |
} | |
else | |
{ | |
set_current_pattern_mask(get_current_pattern_mask() & ~mask); | |
} | |
} | |
void toggle_pattern_enabled_state(int pattern) | |
{ | |
int mask = get_mask(pattern); | |
set_current_pattern_mask(get_current_pattern_mask() ^ mask); | |
} | |
void set_pattern_enabled(int pattern) | |
{ | |
set_pattern_enabled_state(pattern, 1); | |
} | |
void clear_pattern_enabled(int pattern) | |
{ | |
set_pattern_enabled_state(pattern, 0); | |
} | |
int get_pattern_enabled(int pattern) | |
{ | |
int mask = get_mask(pattern); | |
return get_current_pattern_mask() & mask; | |
} | |
void solo_pressed_patterns() | |
{ | |
set_current_pattern_mask(pressed_pads); | |
} | |
void erase_pressed_patterns() | |
{ | |
int i; | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
if(pressed_pads & (1 << i)) clear_pattern(i); | |
} | |
} | |
void set_current_scene(int scene) | |
{ | |
while(scene < 0) scene += MAX_SONG_SCENES; | |
current_scene = scene % MAX_SONG_SCENES; | |
} | |
void increment_current_scene() | |
{ | |
set_current_scene(current_scene + 1); | |
} | |
void decrement_current_scene() | |
{ | |
set_current_scene(current_scene - 1); | |
} | |
int get_pattern_patch(int pattern) | |
{ | |
return song->patterns[pattern].patch; | |
} | |
void change_program(int channel, int program); | |
void set_pattern_patch(int pattern, int patch) | |
{ | |
while(patch < 0) patch += MAX_SONG_PATCHES; | |
song->patterns[pattern].patch = patch % MAX_SONG_PATCHES; | |
change_program(pattern, patch); | |
} | |
void increment_pattern_patch(int pattern) | |
{ | |
int patch = get_pattern_patch(pattern); | |
set_pattern_patch(pattern, patch + 1); | |
} | |
void decrement_pattern_patch(int pattern) | |
{ | |
int patch = get_pattern_patch(pattern); | |
set_pattern_patch(pattern, patch - 1); | |
} | |
int get_first_selected_pattern() | |
{ | |
int i; | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
if(pressed_pads & (1 << i)) return i; | |
} | |
return -1; | |
} | |
void increment_first_selected_patch() | |
{ | |
increment_pattern_patch(get_first_selected_pattern()); | |
} | |
void decrement_first_selected_patch() | |
{ | |
decrement_pattern_patch(get_first_selected_pattern()); | |
} | |
int get_first_selected_patch() | |
{ | |
return get_pattern_patch(get_first_selected_pattern()); | |
} | |
int set_selected_patterns_patch(int patch) | |
{ | |
int i; | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
if(pressed_pads & (1 << i)) set_pattern_patch(i, patch); | |
} | |
} | |
void queue_queue(); | |
void clear_queue(); | |
snd_seq_tick_time_t get_tick(); | |
snd_seq_tick_time_t tick = 0; | |
int toggle_playing() // make all these functions cleaner and just simply return ints | |
{ | |
playing = !playing; | |
clear_queue(); | |
if(playing) | |
{ | |
tick = get_tick(); | |
queue_queue(); | |
} | |
return playing; | |
} | |
int toggle_mode() | |
{ | |
switch(current_mode) | |
{ | |
case MODE_LOOP: | |
current_mode = MODE_RECORD; | |
break; | |
case MODE_RECORD: | |
current_mode = MODE_SONG; | |
break; | |
case MODE_SONG: | |
current_mode = MODE_LOOP; | |
break; | |
} | |
return current_mode; | |
} | |
snd_seq_t *seq_handle; | |
int launch_key_in_port_1_id; | |
int launch_key_in_port_2_id; | |
int midi_out_port_id; | |
int launch_key_out_port_2_id; | |
int queue_id; | |
void pulse_metronome(snd_seq_tick_time_t tick, int color, int sound, int percussion_note); | |
int new_led_color(int red, int green); | |
snd_seq_tick_time_t next_tick; | |
void queue_queue() | |
{ | |
snd_seq_tick_time_t end_tick = song->ticks_per_beat; | |
end_tick *= song->beats_per_measure; | |
end_tick *= song->measures_per_pattern; | |
next_tick = tick + end_tick; | |
snd_seq_event_t event; | |
snd_seq_ev_clear(&event); | |
event.type = SND_SEQ_EVENT_ECHO; | |
snd_seq_ev_schedule_tick(&event, queue_id, 0, next_tick); | |
snd_seq_ev_set_dest(&event, snd_seq_client_id(seq_handle), launch_key_in_port_1_id); | |
snd_seq_event_output_direct(seq_handle, &event); | |
int mask = get_current_pattern_mask(); | |
int count = 0; | |
int i; | |
for(i = 0; i < MAX_SONG_PATTERNS; i++) | |
{ | |
if(mask & (1 << i)) | |
{ | |
int note_on_events_tick[128]; | |
int note_on_events_quantized[128]; | |
int a; | |
for(a = 0; a < 128; a++) | |
{ | |
note_on_events_tick[a] = -1; | |
} | |
for(a = 0; a < song->patterns[i].events_length; a++) | |
{ | |
count++; | |
event = song->patterns[i].events[a]; | |
snd_seq_tick_time_t dt = event.time.tick; | |
if(event.type == SND_SEQ_EVENT_NOTEON) | |
{ | |
note_on_events_tick[event.data.note.note] = dt; | |
int q = song->quantize; | |
q = 1 << q; | |
dt += q / 2; | |
dt /= q; | |
dt *= q; | |
note_on_events_quantized[event.data.note.note] = dt; | |
} | |
else if(event.type == SND_SEQ_EVENT_NOTEOFF) | |
{ | |
if(note_on_events_tick[event.data.note.note] != -1) | |
{ | |
int length = dt - note_on_events_tick[event.data.note.note]; | |
dt = note_on_events_quantized[event.data.note.note] + length; | |
note_on_events_tick[event.data.note.note] = -1; | |
} | |
} | |
snd_seq_ev_schedule_tick(&event, queue_id, 0, tick + dt); | |
snd_seq_ev_set_source(&event, midi_out_port_id); | |
snd_seq_ev_set_subs(&event); | |
snd_seq_event_output_direct(seq_handle, &event); | |
} | |
change_program(i, song->patterns[i].patch); | |
} | |
} | |
for(i = 0; i < song->measures_per_pattern; i++) | |
{ | |
int a; | |
for(a = 0; a < song->beats_per_measure; a++) | |
{ | |
int note; | |
if(i == 0 && a == 0) | |
{ | |
note = 75; | |
} | |
else if(a == 0) | |
{ | |
note = 76; | |
} | |
else | |
{ | |
note = 77; | |
} | |
int beat = a + i * song->beats_per_measure; | |
pulse_metronome(tick + beat * song->ticks_per_beat, a == 0 ? 0 : (i == song->measures_per_pattern - 1 ? 2 : 1), count == 0, note); | |
} | |
} | |
} | |
snd_seq_tick_time_t get_tick() | |
{ | |
snd_seq_queue_status_t *status; | |
snd_seq_tick_time_t current_tick; | |
snd_seq_queue_status_malloc(&status); | |
snd_seq_get_queue_status(seq_handle, queue_id, status); | |
current_tick = snd_seq_queue_status_get_tick_time(status); | |
snd_seq_queue_status_free(status); | |
return current_tick; | |
} | |
void set_tempo(int bpm, int ppq) | |
{ | |
snd_seq_queue_tempo_t *queue_tempo; | |
snd_seq_queue_tempo_malloc(&queue_tempo); | |
int tempo = (int)(6e7 / ((double)bpm * (double)ppq) * (double)ppq); | |
snd_seq_queue_tempo_set_tempo(queue_tempo, tempo); | |
snd_seq_queue_tempo_set_ppq(queue_tempo, ppq); | |
snd_seq_set_queue_tempo(seq_handle, queue_id, queue_tempo); | |
snd_seq_queue_tempo_free(queue_tempo); | |
} | |
void change_tempo(int bpm) | |
{ | |
song->bpm = bpm; | |
set_tempo(song->bpm, song->ticks_per_beat); | |
} | |
int new_queue() | |
{ | |
int id = snd_seq_alloc_queue(seq_handle); | |
snd_seq_set_client_pool_output(seq_handle, MAX_QUEUE_SIZE); | |
return id; | |
} | |
void init_queue() | |
{ | |
queue_id = new_queue(); | |
set_tempo(song->bpm, song->ticks_per_beat); | |
snd_seq_start_queue(seq_handle, queue_id, NULL); | |
snd_seq_drain_output(seq_handle); | |
} | |
void clear_queue() | |
{ | |
snd_seq_remove_events_t *remove_ev; | |
snd_seq_remove_events_malloc(&remove_ev); | |
snd_seq_remove_events_set_queue(remove_ev, queue_id); | |
snd_seq_remove_events_set_condition(remove_ev, SND_SEQ_REMOVE_OUTPUT | SND_SEQ_REMOVE_IGNORE_OFF); | |
snd_seq_remove_events(seq_handle, remove_ev); | |
snd_seq_remove_events_free(remove_ev); | |
} | |
snd_seq_t *open_client() | |
{ | |
snd_seq_t *handle; | |
if(snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) | |
{ | |
fprintf(stderr, "Error opening ALSA sequencer.\n"); | |
return -1; | |
} | |
snd_seq_set_client_name(handle, ALSA_CLIENT_NAME); | |
return handle; | |
} | |
int new_port(snd_seq_t *handle, char *name, int flags) | |
{ | |
int id = snd_seq_create_simple_port(handle, name, flags, SND_SEQ_PORT_TYPE_APPLICATION); | |
if(id < 0) | |
{ | |
fprintf(stderr, "Error creating sequencer port.\n"); | |
return -1; | |
} | |
return id; | |
} | |
int new_in_port(snd_seq_t *handle, char *name) | |
{ | |
return new_port(handle, name, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); | |
} | |
int new_out_port(snd_seq_t *handle, char *name) | |
{ | |
return new_port(handle, name, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ); | |
} | |
int init_alsa() | |
{ | |
if((seq_handle = open_client()) < 0) return -1; | |
if((launch_key_in_port_1_id = new_in_port(seq_handle, "LaunchKey MIDI 1")) < 0) return -1; | |
if((launch_key_in_port_2_id = new_in_port(seq_handle, "LaunchKey MIDI 2")) < 0) return -1; | |
if((midi_out_port_id = new_out_port(seq_handle, "MIDI Out")) < 0) return -1; | |
if((launch_key_out_port_2_id = new_out_port(seq_handle, "LaunchKey MIDI 2")) < 0) return -1; | |
return 0; | |
} | |
void change_program(int channel, int program) | |
{ | |
snd_seq_event_t event; | |
snd_seq_ev_clear(&event); | |
snd_seq_ev_set_pgmchange(&event, channel, program); | |
snd_seq_ev_set_subs(&event); | |
snd_seq_ev_set_source(&event, midi_out_port_id); | |
snd_seq_ev_set_direct(&event); | |
snd_seq_event_output_direct(seq_handle, &event); | |
} | |
int control_note_queue = 0; | |
void play_note(int note, int velocity, int channel, int ticks, int tick) | |
{ | |
snd_seq_event_t event; | |
snd_seq_ev_clear(&event); | |
snd_seq_ev_set_note(&event, channel, note, velocity, ticks); | |
snd_seq_ev_set_subs(&event); | |
snd_seq_ev_set_source(&event, midi_out_port_id); | |
snd_seq_ev_schedule_tick(&event, queue_id, 0, tick); | |
snd_seq_event_output_direct(seq_handle, &event); | |
} | |
void send_control_noteon_with_velocity(int note, int velocity) | |
{ | |
snd_seq_event_t event; | |
snd_seq_ev_clear(&event); | |
snd_seq_ev_set_noteon(&event, 0, note, velocity); | |
snd_seq_ev_set_subs(&event); | |
snd_seq_ev_set_source(&event, launch_key_out_port_2_id); | |
if(control_note_queue) | |
{ | |
snd_seq_ev_schedule_tick(&event, queue_id, 0, control_note_queue); | |
control_note_queue = 0; | |
} | |
else | |
{ | |
snd_seq_ev_set_direct(&event); | |
} | |
snd_seq_event_output_direct(seq_handle, &event); | |
} | |
void send_control_noteon(int note) | |
{ | |
send_control_noteon_with_velocity(note, 127); | |
} | |
void send_control_noteoff(int note) | |
{ | |
snd_seq_event_t event; | |
snd_seq_ev_clear(&event); | |
snd_seq_ev_set_noteoff(&event, 0, note, 0); | |
snd_seq_ev_set_subs(&event); | |
snd_seq_ev_set_direct(&event); | |
snd_seq_ev_set_source(&event, launch_key_out_port_2_id); | |
snd_seq_event_output_direct(seq_handle, &event); | |
} | |
void enable_in_control() | |
{ | |
send_control_noteon(12); | |
} | |
void disable_in_control() | |
{ | |
send_control_noteon_with_velocity(12, 0); | |
} | |
void show_in_control() | |
{ | |
send_control_noteon(10); | |
} | |
void hide_in_control() | |
{ | |
send_control_noteon_with_velocity(10, 0); | |
} | |
int led_id_to_pad_id(led_id) | |
{ | |
switch(led_id) | |
{ | |
case 0: | |
return 96; | |
case 1: | |
return 97; | |
case 2: | |
return 98; | |
case 3: | |
return 99; | |
case 4: | |
return 100; | |
case 5: | |
return 101; | |
case 6: | |
return 102; | |
case 7: | |
return 103; | |
case 8: | |
return 112; | |
case 9: | |
return 113; | |
case 10: | |
return 114; | |
case 11: | |
return 115; | |
case 12: | |
return 116; | |
case 13: | |
return 117; | |
case 14: | |
return 118; | |
case 15: | |
return 119; | |
case 16: | |
return 104; | |
case 17: | |
return 120; | |
default: | |
return 0; | |
} | |
} | |
int new_led_color(int red, int green) | |
{ | |
return red + (green << 4); | |
} | |
void set_led(int led_id, int color) | |
{ | |
int pad_id = led_id_to_pad_id(led_id); | |
send_control_noteon_with_velocity(pad_id, color); | |
} | |
int get_led_id_by_xy(int x, int y) | |
{ | |
if(x < 0 || y < 0 || x > 8 || y > 1) return; | |
if(x == 8) | |
{ | |
return y ? 17 : 16; | |
} | |
else | |
{ | |
return x + y * 8; | |
} | |
} | |
void set_led_xy(int x, int y, int color) | |
{ | |
int led_id = get_led_id_by_xy(x, y); | |
set_led(led_id, color); | |
} | |
void set_led_rect(int x, int y, int w, int h, int color) | |
{ | |
int ox = x; | |
int oy = y; | |
int mx = x+w; | |
int my = y+h; | |
for(x=ox; x<mx; x++) | |
{ | |
for(y=oy; y<my; y++) | |
{ | |
set_led_xy(x, y, color); | |
} | |
} | |
} | |
void set_led_row(int y, int color) | |
{ | |
set_led_rect(0, y, 9, 1, color); | |
} | |
void set_led_column(int x, int color) | |
{ | |
set_led_rect(x, 0, 1, 2, color); | |
} | |
void led_fill(int color) | |
{ | |
set_led_rect(0, 0, 9, 2, color); | |
} | |
void led_clear() | |
{ | |
led_fill(0); | |
} | |
void led_test() | |
{ | |
set_led_column(0, new_led_color(2, 0)); | |
set_led_column(1, new_led_color(3, 0)); | |
set_led_column(2, new_led_color(3, 1)); | |
set_led_column(3, new_led_color(3, 2)); | |
set_led_column(4, new_led_color(3, 3)); | |
set_led_column(5, new_led_color(2, 3)); | |
set_led_column(6, new_led_color(1, 3)); | |
set_led_column(7, new_led_color(0, 3)); | |
set_led_column(8, new_led_color(0, 2)); | |
} | |
void led_test_2() | |
{ | |
set_led_column(8, new_led_color(2, 0)); | |
set_led_column(7, new_led_color(3, 0)); | |
set_led_column(6, new_led_color(3, 1)); | |
set_led_column(5, new_led_color(3, 2)); | |
set_led_column(4, new_led_color(3, 3)); | |
set_led_column(3, new_led_color(2, 3)); | |
set_led_column(2, new_led_color(1, 3)); | |
set_led_column(1, new_led_color(0, 3)); | |
set_led_column(0, new_led_color(0, 2)); | |
} | |
void led_test_3() | |
{ | |
set_led_row(0, new_led_color(3, 0)); | |
set_led_row(1, new_led_color(0, 3)); | |
} | |
void led_test_4() | |
{ | |
set_led_xy(0, 0, new_led_color(0, 0)); | |
set_led_xy(1, 0, new_led_color(0, 1)); | |
set_led_xy(2, 0, new_led_color(0, 2)); | |
set_led_xy(3, 0, new_led_color(0, 3)); | |
set_led_xy(0, 1, new_led_color(0, 0)); | |
set_led_xy(1, 1, new_led_color(1, 1)); | |
set_led_xy(2, 1, new_led_color(2, 2)); | |
set_led_xy(3, 1, new_led_color(3, 3)); | |
set_led_xy(4, 0, new_led_color(0, 0)); | |
set_led_xy(5, 0, new_led_color(1, 0)); | |
set_led_xy(6, 0, new_led_color(2, 0)); | |
set_led_xy(7, 0, new_led_color(3, 0)); | |
set_led_xy(4, 1, new_led_color(4, 1)); | |
set_led_xy(5, 1, new_led_color(1, 4)); | |
set_led_xy(6, 1, new_led_color(3, 1)); | |
set_led_xy(7, 1, new_led_color(1, 3)); | |
set_led_xy(8, 0, new_led_color(3, 2)); | |
set_led_xy(8, 1, new_led_color(2, 3)); | |
} | |
void pulse_metronome(snd_seq_tick_time_t tick, int color, int sound, int percussion_note) | |
{ | |
int i = 0; | |
int brightness = 4; | |
while(brightness) | |
{ | |
brightness--; | |
int c; | |
if(color == 0) | |
{ | |
c = new_led_color(brightness, 0); | |
} | |
else if(color == 1) | |
{ | |
c = new_led_color(0, brightness); | |
} | |
else if(color == 2) | |
{ | |
c = new_led_color(brightness, brightness); | |
} | |
control_note_queue = tick + i * (song->ticks_per_beat / METRONOME_PULSE_FRAMES_PER_BEAT); | |
set_led_xy(8, 0, c); | |
i++; | |
} | |
if(sound) play_note(percussion_note, 63, 9, 10, tick); | |
} | |
int get_pad_x(int pad) | |
{ | |
if(pad < 16) | |
{ | |
return pad % 8; | |
} | |
else | |
{ | |
return 8; | |
} | |
} | |
int get_pad_y(int pad) | |
{ | |
if(pad < 16) | |
{ | |
return pad / 8; | |
} | |
else if(pad == 16) | |
{ | |
return 0; | |
} | |
else if(pad == 17) | |
{ | |
return 1; | |
} | |
} | |
int set_digit(number, base, digit, replacement_value) | |
{ | |
int i; | |
for(i = 0; i < digit; i++) | |
{ | |
replacement_value *= base; | |
} | |
int place_holder_value = number; | |
for(i = 0; i < digit; i++) | |
{ | |
place_holder_value /= base; | |
} | |
place_holder_value %= base; | |
for(i = 0; i < digit; i++) | |
{ | |
place_holder_value *= base; | |
} | |
number -= place_holder_value; | |
number += replacement_value; | |
return number; | |
} | |
void display_number(number) | |
{ | |
led_clear(); | |
int digit_0 = number % 8; | |
number /= 8; | |
int digit_1 = number % 8; | |
number /= 8; | |
int digit_2 = number % 2; | |
set_led_xy(digit_0, 0, new_led_color(3, 0)); | |
set_led_xy(digit_1, 1, new_led_color(3, 3)); | |
set_led_xy(8, digit_2, new_led_color(1, 3)); | |
} | |
int replace_number_screen_value(num) | |
{ | |
if(current_screen == SCREEN_SONG) | |
{ | |
current_scene = num; | |
} | |
else if(current_screen == SCREEN_PATCH) | |
{ | |
set_selected_patterns_patch(num); | |
} | |
display_number(num); | |
} | |
int get_number_screen_value() | |
{ | |
int num; | |
if(current_screen == SCREEN_SONG) | |
{ | |
num = current_scene; | |
} | |
else if(current_screen == SCREEN_PATCH) | |
{ | |
num = get_first_selected_patch(); | |
} | |
return num; | |
} | |
void pattern_screen_update_play_button() | |
{ | |
if(playing) | |
{ | |
set_led(16, new_led_color(0, 3)); | |
} | |
else | |
{ | |
set_led(16, new_led_color(0, 0)); | |
} | |
} | |
void pattern_screen_update_mode_button() | |
{ | |
switch(current_mode) | |
{ | |
case MODE_SONG: | |
set_led(17, new_led_color(0, 0)); | |
break; | |
case MODE_LOOP: | |
set_led(17, new_led_color(0, 3)); | |
break; | |
case MODE_RECORD: | |
set_led(17, new_led_color(3, 0)); | |
break; | |
} | |
} | |
void pattern_screen_update_pad(int pad) | |
{ | |
if(pad < 16) | |
{ | |
if(get_pad_pressed(pad)) | |
{ | |
set_led(pad, new_led_color(3, 0)); | |
} | |
else | |
{ | |
if(get_pattern_enabled(pad)) | |
{ | |
if(song->patterns[pad].events_length) | |
{ | |
set_led(pad, new_led_color(3, 3)); | |
} | |
else | |
{ | |
set_led(pad, new_led_color(0, 3)); | |
} | |
} | |
else | |
{ | |
if(song->patterns[pad].events_length) | |
{ | |
set_led(pad, new_led_color(0, 0)); | |
} | |
else | |
{ | |
set_led(pad, new_led_color(0, 0)); | |
} | |
} | |
} | |
} | |
else if(pad == 16) | |
{ | |
pattern_screen_update_play_button(); | |
} | |
else if(pad == 17) | |
{ | |
pattern_screen_update_mode_button(); | |
} | |
} | |
void pattern_screen_update_pads() | |
{ | |
int i; | |
for(i = 0; i < 18; i++) | |
{ | |
pattern_screen_update_pad(i); | |
} | |
} | |
void pattern_screen() | |
{ | |
current_screen = SCREEN_PATTERN; | |
pattern_screen_update_pads(); | |
} | |
void patch_screen() | |
{ | |
current_screen = SCREEN_PATCH; | |
first_pressed_pad = get_first_selected_pattern(); | |
display_number(get_first_selected_patch()); | |
} | |
void song_screen() | |
{ | |
current_screen = SCREEN_SONG; | |
display_number(current_scene); | |
} | |
void init_screen() | |
{ | |
enable_in_control(); | |
pattern_screen(); | |
} | |
void play_press() | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
if(pressed_pads) | |
{ | |
solo_pressed_patterns(); | |
pressed_pads = 0; | |
pattern_screen_update_pads(); | |
} | |
else | |
{ | |
toggle_playing(); | |
pattern_screen_update_play_button(); | |
} | |
break; | |
case SCREEN_SONG: | |
case SCREEN_PATCH: ; | |
int num = get_number_screen_value(); | |
num = set_digit(num, 8, 2, 0); | |
replace_number_screen_value(num); | |
break; | |
} | |
} | |
void play_release() | |
{ | |
} | |
void mode_press() | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
if(pressed_pads) | |
{ | |
erase_pressed_patterns(); | |
pressed_pads = 0; | |
pattern_screen_update_pads(); | |
} | |
else | |
{ | |
toggle_mode(); | |
pattern_screen_update_mode_button(); | |
} | |
break; | |
case SCREEN_SONG: | |
case SCREEN_PATCH: ; | |
int num = get_number_screen_value(); | |
num = set_digit(num, 8, 2, 1); | |
replace_number_screen_value(num); | |
break; | |
} | |
} | |
void mode_release() | |
{ | |
} | |
void pad_press(int pad) | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
set_pad_pressed(pad); | |
pattern_screen_update_pad(pad); | |
break; | |
case SCREEN_SONG: | |
case SCREEN_PATCH: ; | |
int x = get_pad_x(pad); | |
int y = get_pad_y(pad); | |
int num = get_number_screen_value(); | |
num = set_digit(num, 8, y, x); | |
replace_number_screen_value(num); | |
break; | |
} | |
} | |
void pad_release(int pad) | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
if(get_pad_pressed(pad)) | |
{ | |
clear_pad_pressed(pad); | |
toggle_pattern_enabled_state(pad); | |
pattern_screen_update_pad(pad); | |
} | |
break; | |
case SCREEN_SONG: | |
break; | |
case SCREEN_PATCH: ; | |
if(pad == first_pressed_pad) | |
{ | |
pressed_pads = 0; | |
pattern_screen(); | |
} | |
break; | |
} | |
} | |
void arrow_presed(int up) | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
if(pressed_pads) | |
{ | |
patch_screen(); | |
int num = get_number_screen_value(); | |
if(up) | |
{ | |
num = (num + 1) % MAX_SONG_PATCHES; | |
} | |
else | |
{ | |
num--; | |
while(num < 0) num += MAX_SONG_PATCHES; | |
} | |
replace_number_screen_value(num); | |
} | |
else | |
{ | |
if(up) | |
{ | |
increment_current_scene(); | |
} | |
else | |
{ | |
decrement_current_scene(); | |
} | |
song_screen(); | |
} | |
break; | |
case SCREEN_SONG: | |
break; | |
case SCREEN_PATCH: ; | |
int num = get_number_screen_value(); | |
if(up) | |
{ | |
num = (num + 1) % MAX_SONG_PATCHES; | |
} | |
else | |
{ | |
num--; | |
while(num < 0) num += MAX_SONG_PATCHES; | |
} | |
replace_number_screen_value(num); | |
break; | |
} | |
} | |
void left() | |
{ | |
arrow_presed(0); | |
} | |
void right() | |
{ | |
arrow_presed(1); | |
} | |
void up() | |
{ | |
right(); | |
} | |
void down() | |
{ | |
left(); | |
} | |
void horizontal_release() | |
{ | |
switch(current_screen) | |
{ | |
case SCREEN_PATTERN: | |
case SCREEN_PATCH: | |
break; | |
case SCREEN_SONG: ; | |
pressed_pads = 0; | |
pattern_screen(); | |
break; | |
} | |
} | |
void vertical_release() | |
{ | |
horizontal_release(); | |
} | |
void pass_event(snd_seq_event_t *event) | |
{ | |
snd_seq_ev_set_subs(event); | |
snd_seq_ev_set_direct(event); | |
snd_seq_ev_set_source(event, midi_out_port_id); | |
snd_seq_event_output_direct(seq_handle, event); | |
if(current_mode == MODE_RECORD) | |
{ | |
int chan = event->data.note.channel; | |
if(!playing) | |
{ | |
toggle_playing(); | |
pattern_screen_update_play_button(); | |
} | |
snd_seq_event_t e = *event; | |
snd_seq_tick_time_t dt = get_tick() - tick; | |
e.time.tick = dt; | |
record_event(chan, e); | |
if(current_screen == SCREEN_PATTERN) | |
{ | |
set_pattern_enabled(chan); | |
pattern_screen_update_pad(chan); | |
} | |
} | |
} | |
void handle_event(snd_seq_event_t *event) | |
{ | |
int is_port_1 = event->dest.port == launch_key_in_port_1_id ? 1 : 0; | |
int is_port_2 = event->dest.port == launch_key_in_port_2_id ? 1 : 0; | |
switch(event->type) | |
{ | |
case SND_SEQ_EVENT_PORT_SUBSCRIBED: | |
case SND_SEQ_EVENT_PORT_CHANGE: | |
case SND_SEQ_EVENT_CLIENT_START: | |
case SND_SEQ_EVENT_CLIENT_CHANGE: | |
init_screen(); | |
break; | |
case SND_SEQ_EVENT_ECHO: | |
if(current_mode == MODE_SONG) | |
{ | |
increment_current_scene(); | |
if(current_screen == SCREEN_PATTERN) | |
{ | |
pattern_screen_update_pads(); | |
} | |
} | |
tick = next_tick; | |
queue_queue(); | |
break; | |
case SND_SEQ_EVENT_NOTEON: | |
if(is_port_2) | |
{ | |
switch(event->data.note.note) | |
{ | |
case 10: | |
pattern_screen(); | |
break; | |
case 104: | |
play_press(); | |
break; | |
case 120: | |
mode_press(); | |
break; | |
case 96: | |
pad_press(0); | |
break; | |
case 97: | |
pad_press(1); | |
break; | |
case 98: | |
pad_press(2); | |
break; | |
case 99: | |
pad_press(3); | |
break; | |
case 100: | |
pad_press(4); | |
break; | |
case 101: | |
pad_press(5); | |
break; | |
case 102: | |
pad_press(6); | |
break; | |
case 103: | |
pad_press(7); | |
break; | |
case 112: | |
pad_press(8); | |
break; | |
case 113: | |
pad_press(9); | |
break; | |
case 114: | |
pad_press(10); | |
break; | |
case 115: | |
pad_press(11); | |
break; | |
case 116: | |
pad_press(12); | |
break; | |
case 117: | |
pad_press(13); | |
break; | |
case 118: | |
pad_press(14); | |
break; | |
case 119: | |
pad_press(15); | |
break; | |
} | |
} | |
else | |
{ | |
pass_event(event); | |
} | |
break; | |
case SND_SEQ_EVENT_NOTEOFF: | |
if(is_port_2) | |
{ | |
switch(event->data.note.note) | |
{ | |
case 104: | |
play_release(); | |
break; | |
case 120: | |
mode_release(); | |
break; | |
case 96: | |
pad_release(0); | |
break; | |
case 97: | |
pad_release(1); | |
break; | |
case 98: | |
pad_release(2); | |
break; | |
case 99: | |
pad_release(3); | |
break; | |
case 100: | |
pad_release(4); | |
break; | |
case 101: | |
pad_release(5); | |
break; | |
case 102: | |
pad_release(6); | |
break; | |
case 103: | |
pad_release(7); | |
break; | |
case 112: | |
pad_release(8); | |
break; | |
case 113: | |
pad_release(9); | |
break; | |
case 114: | |
pad_release(10); | |
break; | |
case 115: | |
pad_release(11); | |
break; | |
case 116: | |
pad_release(12); | |
break; | |
case 117: | |
pad_release(13); | |
break; | |
case 118: | |
pad_release(14); | |
break; | |
case 119: | |
pad_release(15); | |
break; | |
} | |
} | |
else | |
{ | |
pass_event(event); | |
} | |
break; | |
case SND_SEQ_EVENT_CONTROLLER: | |
if(is_port_2) | |
{ | |
switch(event->data.control.param) | |
{ | |
case 27: | |
song->quantize = event->data.control.value / 16; | |
break; | |
case 28: | |
change_tempo(event->data.control.value * 3 / 2 + 32); | |
break; | |
} | |
if(event->data.control.value) | |
{ | |
switch(event->data.control.param) | |
{ | |
case 104: | |
up(); | |
break; | |
case 105: | |
down(); | |
break; | |
case 106: | |
left(); | |
break; | |
case 107: | |
right(); | |
break; | |
} | |
} | |
else | |
{ | |
switch(event->data.control.param) | |
{ | |
case 104: | |
case 105: | |
vertical_release(); | |
break; | |
case 106: | |
case 107: | |
horizontal_release(); | |
break; | |
} | |
} | |
} | |
else | |
{ | |
switch(event->data.control.param) | |
{ | |
case 104: | |
case 105: | |
case 106: | |
case 107: | |
init_screen(); | |
break; | |
} | |
pass_event(event); | |
} | |
break; | |
case SND_SEQ_EVENT_PITCHBEND: | |
pass_event(event); | |
break; | |
} | |
} | |
void sigterm_exit(int sig) | |
{ | |
printf("Cleaning up...\n"); | |
sleep(1); | |
snd_seq_stop_queue(seq_handle, queue_id, NULL); | |
snd_seq_free_queue(seq_handle, queue_id); | |
exit(0); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
if(init_alsa() < 0) | |
{ | |
fprintf(stderr, "Error initializing.\n"); | |
exit(1); | |
} | |
song = new_song(); | |
init_queue(); | |
signal(SIGINT, sigterm_exit); | |
signal(SIGTERM, sigterm_exit); | |
int l1; | |
int npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); | |
struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); | |
snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN); | |
snd_seq_event_t *event; | |
while(1) | |
{ | |
if(poll(pfd, npfd, 100000) > 0) | |
{ | |
for(l1 = 0; l1 < npfd; l1++) | |
{ | |
if(pfd[l1].revents > 0) | |
{ | |
do | |
{ | |
snd_seq_event_input(seq_handle, &event); | |
handle_event(event); | |
snd_seq_free_event(event); | |
} while(snd_seq_event_input_pending(seq_handle, 0) > 0); | |
} | |
} | |
} | |
} | |
return 0; | |
} |
Hi,
has anyone used this?
Would be amazing to have a step sequencer in ableton live
I sorta quit this and bought Remotify's Control Surface Studio
Kinda buggy and annoying but it does the job very well
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you
I'll try it out with my LaunchKey Mini MK3
I haven't managed to make Ableton Live 11 work under WINE, I wonder if I could port this to run on MacOS somehow 🤔