tap tempo
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
TARGET ?= forge
|
TARGET ?= forge
|
||||||
INSTALL_DIR ?= $(HOME)/.local/bin
|
INSTALL_DIR ?= $(HOME)/.local/bin
|
||||||
TEST_ARGS ?= --frag=./shaders --config=./config/forge.cfg --video-in=/dev/video0 --video-in=/dev/video1 --video-in=/dev/video2 --video-in=/dev/video3 --video-in=/dev/video4 --video-in=/dev/video5 --video-in=/dev/video6 --video-in=/dev/video7 --video-in=/dev/video8 --video-in=/dev/video9 --tempo=30
|
TEST_ARGS ?= --frag=./shaders --config=./config/forge.cfg --video-in=/dev/video0 --video-in=/dev/video1 --video-in=/dev/video2 --video-in=/dev/video3 --video-in=/dev/video4 --video-in=/dev/video5 --video-in=/dev/video6 --video-in=/dev/video7 --video-in=/dev/video8 --video-in=/dev/video9
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ make -f Makefile.dev release-arch
|
|||||||
- [x] Send midi data to shaders
|
- [x] Send midi data to shaders
|
||||||
- [ ] Save midi state
|
- [ ] Save midi state
|
||||||
- [x] State machine with A/B switch
|
- [x] State machine with A/B switch
|
||||||
- [ ] Tap-tempo feature
|
- [x] Tap-tempo feature
|
||||||
- [ ] Clean code and fix things
|
- [ ] Clean code and fix things
|
||||||
- [x] Video input
|
- [x] Video input
|
||||||
- [x] Fixed camera video
|
- [x] Fixed camera video
|
||||||
@@ -151,5 +151,6 @@ make -f Makefile.dev release-arch
|
|||||||
- [x] Clean code and fix things
|
- [x] Clean code and fix things
|
||||||
- [x] Share openGL state between monitor and screen
|
- [x] Share openGL state between monitor and screen
|
||||||
- [ ] Other
|
- [ ] Other
|
||||||
|
- [ ] Update readme with usage documentation
|
||||||
- [ ] Clone "shaders" and config in system path at setup
|
- [ ] Clone "shaders" and config in system path at setup
|
||||||
- [ ] Find and fix opengl errors 0500 ?
|
- [ ] Find and fix opengl errors 0500 ?
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
|
/* PACKAGE */
|
||||||
|
|
||||||
#ifndef PACKAGE
|
#ifndef PACKAGE
|
||||||
#define PACKAGE "forge"
|
#define PACKAGE "forge"
|
||||||
#endif /* PACKAGE */
|
#endif /* PACKAGE */
|
||||||
@@ -9,6 +11,8 @@
|
|||||||
#define VERSION "(dev)"
|
#define VERSION "(dev)"
|
||||||
#endif /* VERSION */
|
#endif /* VERSION */
|
||||||
|
|
||||||
|
/* TYPES */
|
||||||
|
|
||||||
#ifndef MAX_VIDEO
|
#ifndef MAX_VIDEO
|
||||||
#define MAX_VIDEO 16
|
#define MAX_VIDEO 16
|
||||||
#endif
|
#endif
|
||||||
@@ -17,6 +21,8 @@
|
|||||||
#define MAX_FRAG 64
|
#define MAX_FRAG 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* MIDI */
|
||||||
|
|
||||||
#ifndef UNSET_MIDI_CODE
|
#ifndef UNSET_MIDI_CODE
|
||||||
#define UNSET_MIDI_CODE 300
|
#define UNSET_MIDI_CODE 300
|
||||||
#endif
|
#endif
|
||||||
@@ -25,6 +31,8 @@
|
|||||||
#define MIDI_MAX 127
|
#define MIDI_MAX 127
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* ARRAY */
|
||||||
|
|
||||||
#ifndef ARRAY_SIZE
|
#ifndef ARRAY_SIZE
|
||||||
#define ARRAY_SIZE 1024
|
#define ARRAY_SIZE 1024
|
||||||
#endif
|
#endif
|
||||||
@@ -33,4 +41,36 @@
|
|||||||
#define ARRAY_NOT_FOUND ARRAY_SIZE + 1
|
#define ARRAY_NOT_FOUND ARRAY_SIZE + 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* TEMPO */
|
||||||
|
|
||||||
|
#ifndef MAX_BEAT_LENGTH
|
||||||
|
// 30.0 bpm
|
||||||
|
#define MAX_BEAT_LENGTH 2000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MIN_BEAT_LENGTH
|
||||||
|
// 240.0 bpm
|
||||||
|
#define MIN_BEAT_LENGTH 250
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef BEATS_UNTIL_CHAIN_RESET
|
||||||
|
#define BEATS_UNTIL_CHAIN_RESET 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TOTAL_TAP_VALUES
|
||||||
|
#define TOTAL_TAP_VALUES 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SKIPPED_TAP_THRESHOLD_LOW
|
||||||
|
#define SKIPPED_TAP_THRESHOLD_LOW 1.75
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SKIPPED_TAP_THRESHOLD_HIGH
|
||||||
|
#define SKIPPED_TAP_THRESHOLD_HIGH 2.75
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_TAP_VALUES
|
||||||
|
#define MAX_TAP_VALUES 10
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* CONFIG_H */
|
#endif /* CONFIG_H */
|
||||||
+5
-2
@@ -1,10 +1,11 @@
|
|||||||
#include <log.h>
|
#include <log.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include "arr.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "config_file.h"
|
#include "config_file.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
#include "shaders.h"
|
#include "shaders.h"
|
||||||
#include "shared.h"
|
#include "shared.h"
|
||||||
#include "state.h"
|
#include "state.h"
|
||||||
|
#include "tempo.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
@@ -58,7 +60,8 @@ static void init_context(Parameters params, unsigned int in_count,
|
|||||||
unsigned int frag_count) {
|
unsigned int frag_count) {
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
context->tempo = params.base_tempo;
|
context->tempo = tempo_init();
|
||||||
|
tempo_set(&context->tempo, params.base_tempo);
|
||||||
context->demo = params.demo;
|
context->demo = params.demo;
|
||||||
context->monitor = params.monitor;
|
context->monitor = params.monitor;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -514,7 +514,7 @@ static void use_program(ShaderProgram program, int i, bool output,
|
|||||||
}
|
}
|
||||||
// set fragment uniforms
|
// set fragment uniforms
|
||||||
write_uniform_1f(program.itime_locations[i], context->time);
|
write_uniform_1f(program.itime_locations[i], context->time);
|
||||||
write_uniform_1f(program.itempo_locations[i], context->tempo);
|
write_uniform_1f(program.itempo_locations[i], context->tempo.tempo);
|
||||||
write_uniform_1i(program.ifps_locations[i], context->fps);
|
write_uniform_1i(program.ifps_locations[i], context->fps);
|
||||||
write_uniform_1i(program.idemo_locations[i], context->demo ? 1 : 0);
|
write_uniform_1i(program.idemo_locations[i], context->demo ? 1 : 0);
|
||||||
write_uniform_1i(program.ipage_locations[i], context->page);
|
write_uniform_1i(program.ipage_locations[i], context->page);
|
||||||
|
|||||||
+24
-1
@@ -6,6 +6,7 @@
|
|||||||
#include "midi.h"
|
#include "midi.h"
|
||||||
#include "rand.h"
|
#include "rand.h"
|
||||||
#include "state.h"
|
#include "state.h"
|
||||||
|
#include "tempo.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
StateConfig state_parse_config(ConfigFile config) {
|
StateConfig state_parse_config(ConfigFile config) {
|
||||||
@@ -250,6 +251,14 @@ void state_apply_event(SharedContext *context, StateConfig state_config,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (code == state_config.tap_tempo_code) {
|
||||||
|
found = true;
|
||||||
|
midi_write(midi, code, value);
|
||||||
|
if (value > 0) {
|
||||||
|
tempo_tap(&context->tempo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
log_trace("unknown midi: %d %d", code, value);
|
log_trace("unknown midi: %d %d", code, value);
|
||||||
midi_write(midi, code, value);
|
midi_write(midi, code, value);
|
||||||
@@ -261,6 +270,7 @@ void state_apply_event(SharedContext *context, StateConfig state_config,
|
|||||||
bool state_background_midi_write(SharedContext *context,
|
bool state_background_midi_write(SharedContext *context,
|
||||||
StateConfig state_config, MidiDevice midi) {
|
StateConfig state_config, MidiDevice midi) {
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
bool beat_active, last_active;
|
||||||
|
|
||||||
pid = fork();
|
pid = fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
@@ -276,8 +286,21 @@ bool state_background_midi_write(SharedContext *context,
|
|||||||
update_active(context, state_config, midi);
|
update_active(context, state_config, midi);
|
||||||
update_values(context, state_config, midi);
|
update_values(context, state_config, midi);
|
||||||
|
|
||||||
|
last_active = false;
|
||||||
|
|
||||||
while (!context->stop) {
|
while (!context->stop) {
|
||||||
// TODO tap tempo and more
|
beat_active = tempo_progress(context->tempo) < 0.25;
|
||||||
|
|
||||||
|
if (beat_active != last_active) {
|
||||||
|
safe_midi_write(midi, state_config.tap_tempo_code,
|
||||||
|
beat_active ? MIDI_MAX : 0);
|
||||||
|
|
||||||
|
safe_midi_write(midi,
|
||||||
|
state_config.select_frag_codes.values[context->selected],
|
||||||
|
beat_active ? MIDI_MAX : 0);
|
||||||
|
|
||||||
|
last_active = beat_active;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("(state) background writing stopped by main thread (pid: %d)", pid);
|
log_info("(state) background writing stopped by main thread (pid: %d)", pid);
|
||||||
|
|||||||
+122
-1
@@ -1 +1,122 @@
|
|||||||
#include "tempo.h"
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "tempo.h"
|
||||||
|
|
||||||
|
static long now() {
|
||||||
|
struct timeval now;
|
||||||
|
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
|
||||||
|
return now.tv_sec * 1000 + now.tv_usec / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset_tap_chain(Tempo *tempo, long t) {
|
||||||
|
tempo->last_reset = t;
|
||||||
|
tempo->last_tap = 0;
|
||||||
|
tempo->taps_in_chain = 0;
|
||||||
|
tempo->tap_duration_index = 0;
|
||||||
|
tempo->last_tap_skipped = false;
|
||||||
|
|
||||||
|
memset(tempo->tap_durations, 0, sizeof(tempo->tap_durations));
|
||||||
|
}
|
||||||
|
|
||||||
|
Tempo tempo_init() {
|
||||||
|
Tempo tempo;
|
||||||
|
long t;
|
||||||
|
|
||||||
|
t = now();
|
||||||
|
|
||||||
|
reset_tap_chain(&tempo, t);
|
||||||
|
|
||||||
|
return tempo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_chain_active(Tempo tempo, long t) {
|
||||||
|
return tempo.last_tap + MAX_BEAT_LENGTH > t &&
|
||||||
|
tempo.last_tap + (tempo.beat_length * BEATS_UNTIL_CHAIN_RESET) > t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long get_average_tap_duration(Tempo tempo) {
|
||||||
|
unsigned int amount, i;
|
||||||
|
unsigned long running_total, average_tap_duration;
|
||||||
|
|
||||||
|
amount = tempo.taps_in_chain - 1;
|
||||||
|
if (amount > TOTAL_TAP_VALUES) {
|
||||||
|
amount = TOTAL_TAP_VALUES;
|
||||||
|
}
|
||||||
|
|
||||||
|
running_total = 0;
|
||||||
|
for (i = 0; i < amount; i++) {
|
||||||
|
running_total += tempo.tap_durations[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
average_tap_duration = running_total / amount;
|
||||||
|
if (average_tap_duration < MIN_BEAT_LENGTH) {
|
||||||
|
log_debug("%ld", average_tap_duration);
|
||||||
|
return MIN_BEAT_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return average_tap_duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_tap_to_chain(Tempo *tempo, long t) {
|
||||||
|
long duration;
|
||||||
|
|
||||||
|
duration = t - tempo->last_tap;
|
||||||
|
|
||||||
|
tempo->last_tap = t;
|
||||||
|
|
||||||
|
tempo->taps_in_chain++;
|
||||||
|
|
||||||
|
if (tempo->taps_in_chain == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempo->taps_in_chain > 2 && !tempo->last_tap_skipped &&
|
||||||
|
duration > tempo->beat_length * SKIPPED_TAP_THRESHOLD_LOW &&
|
||||||
|
duration < tempo->beat_length * SKIPPED_TAP_THRESHOLD_HIGH) {
|
||||||
|
duration = duration >> 1;
|
||||||
|
tempo->last_tap_skipped = true;
|
||||||
|
} else {
|
||||||
|
tempo->last_tap_skipped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tempo->tap_durations[tempo->tap_duration_index++] = duration;
|
||||||
|
|
||||||
|
if (tempo->tap_duration_index == TOTAL_TAP_VALUES) {
|
||||||
|
tempo->tap_duration_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tempo->beat_length = get_average_tap_duration(*tempo);
|
||||||
|
|
||||||
|
tempo->tempo = 60000.0 / tempo->beat_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tempo_set(Tempo *tempo, float value) {
|
||||||
|
tempo->tempo = value;
|
||||||
|
tempo->beat_length = 60000.0 / value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tempo_tap(Tempo *tempo) {
|
||||||
|
long t;
|
||||||
|
|
||||||
|
t = now();
|
||||||
|
|
||||||
|
if (!is_chain_active(*tempo, t)) {
|
||||||
|
reset_tap_chain(tempo, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_tap_to_chain(tempo, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
double tempo_progress(Tempo tempo) {
|
||||||
|
long t;
|
||||||
|
|
||||||
|
t = now();
|
||||||
|
|
||||||
|
return fmod((double)(t - tempo.last_reset) / (double)tempo.beat_length, 1.0);
|
||||||
|
}
|
||||||
@@ -3,4 +3,12 @@
|
|||||||
#ifndef TEMPO_H
|
#ifndef TEMPO_H
|
||||||
#define TEMPO_H
|
#define TEMPO_H
|
||||||
|
|
||||||
|
Tempo tempo_init();
|
||||||
|
|
||||||
|
void tempo_tap(Tempo *tempo);
|
||||||
|
|
||||||
|
void tempo_set(Tempo *tempo, float value);
|
||||||
|
|
||||||
|
double tempo_progress(Tempo tempo);
|
||||||
|
|
||||||
#endif /* TEMPO_H */
|
#endif /* TEMPO_H */
|
||||||
+12
-1
@@ -135,6 +135,17 @@ typedef ARRAY(VideoCaptureArray, VideoCapture);
|
|||||||
|
|
||||||
typedef GLFWwindow Window;
|
typedef GLFWwindow Window;
|
||||||
|
|
||||||
|
typedef struct Tempo {
|
||||||
|
long last_reset;
|
||||||
|
long last_tap;
|
||||||
|
unsigned int taps_in_chain;
|
||||||
|
unsigned int tap_duration_index;
|
||||||
|
unsigned int tap_durations[MAX_TAP_VALUES];
|
||||||
|
bool last_tap_skipped;
|
||||||
|
unsigned long beat_length;
|
||||||
|
float tempo;
|
||||||
|
} Tempo;
|
||||||
|
|
||||||
typedef struct SharedContext {
|
typedef struct SharedContext {
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
@@ -145,7 +156,7 @@ typedef struct SharedContext {
|
|||||||
unsigned int internal_height;
|
unsigned int internal_height;
|
||||||
double time;
|
double time;
|
||||||
unsigned int fps;
|
unsigned int fps;
|
||||||
float tempo;
|
Tempo tempo;
|
||||||
// TODO use array
|
// TODO use array
|
||||||
unsigned int state[MAX_FRAG];
|
unsigned int state[MAX_FRAG];
|
||||||
unsigned int page;
|
unsigned int page;
|
||||||
|
|||||||
+1
-2
@@ -4,14 +4,13 @@
|
|||||||
#include <log.h>
|
#include <log.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "shared.h"
|
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
#include "window.h"
|
|
||||||
|
|
||||||
static void ioctl_error(VideoCapture *video_capture, const char *operation,
|
static void ioctl_error(VideoCapture *video_capture, const char *operation,
|
||||||
const char *default_msg) {
|
const char *default_msg) {
|
||||||
|
|||||||
Reference in New Issue
Block a user