From c57f01aa24fa8d1e80d899f6e88620d605954754 Mon Sep 17 00:00:00 2001 From: helgrima Date: Fri, 7 Dec 2018 10:12:20 +0200 Subject: [PATCH 1/7] rocket library and skeleton class for sync --- .vscode/tasks.json | 5 + configuration.json | 3 +- include/Configuration.h | 3 +- include/Cosmonaut.h | 28 ++ libs/Configuration.cpp | 6 +- libs/Cosmonaut.cpp | 25 ++ libs/base.h | 35 +++ libs/device.c | 599 ++++++++++++++++++++++++++++++++++++++++ libs/device.h | 54 ++++ libs/sync.h | 55 ++++ libs/track.c | 121 ++++++++ libs/track.h | 47 ++++ 12 files changed, 977 insertions(+), 4 deletions(-) create mode 100644 include/Cosmonaut.h create mode 100644 libs/Cosmonaut.cpp create mode 100644 libs/base.h create mode 100644 libs/device.c create mode 100644 libs/device.h create mode 100644 libs/sync.h create mode 100644 libs/track.c create mode 100644 libs/track.h diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bc10bbd..4d2bc90 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -44,6 +44,9 @@ "type": "shell", "command": "g++.exe", "args": [ + "libs/device.c", + "libs/track.c", + "libs/Cosmonaut.cpp", "libs/jsoncpp.cpp", "libs/Configuration.cpp", "libs/Music.cpp", @@ -61,11 +64,13 @@ "-lbass", "-ID:\\development\\glew-2.1.0\\include", "-ID:\\development\\freeglut\\include", + "-I${workspaceFolder}\\libs", "-I${workspaceFolder}\\libs\\json", "-I${workspaceFolder}\\include", "-ID:\\development\\bass24\\c", "-static-libgcc", "-static-libstdc++", + "-fpermissive", "-o", "bin\\debug\\shader-system.exe" ], diff --git a/configuration.json b/configuration.json index a592946..51aa20e 100644 --- a/configuration.json +++ b/configuration.json @@ -10,7 +10,8 @@ }, "music": { "file": "assets\\music\\demo-release.mp3", - "BPM": 120, + "BPM": 120.0, + "RPB": 8, "frequency": 44000 }, "shaders": { diff --git a/include/Configuration.h b/include/Configuration.h index bca9563..060850e 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -18,7 +18,8 @@ namespace DemoSystem { struct Tune { std::string file; - int BPM; + double BPM; + int RPB; int frequency; }; diff --git a/include/Cosmonaut.h b/include/Cosmonaut.h new file mode 100644 index 0000000..bbefbba --- /dev/null +++ b/include/Cosmonaut.h @@ -0,0 +1,28 @@ +#ifndef COSMONAUT_H +#define COSMONAUT_H +#include +#include + +namespace DemoSystem { + class Cosmonaut { + public: + Cosmonaut(); + ~Cosmonaut(); + void initialize(double bpm, int rpb); + bool connectPlayer(std::string host); + + private: + bool player; + std::string host; + double BPM; + int RPB; + double rowRate; + + sync_device* device; + }; + + + +} + +#endif \ No newline at end of file diff --git a/libs/Configuration.cpp b/libs/Configuration.cpp index c334629..552ab08 100644 --- a/libs/Configuration.cpp +++ b/libs/Configuration.cpp @@ -10,7 +10,8 @@ DemoSystem::Configuration::Configuration() { this->screen.FPS = 60; this->tune.file = "tune.mp3"; - this->tune.BPM = 80; + this->tune.BPM = 80.0; + this->tune.RPB = 8; this->tune.frequency = 44000; this->shaders.vertex = "shaders/vertex.glsl"; @@ -33,7 +34,8 @@ bool DemoSystem::Configuration::read(std::string file) { this->screen.FPS = c["screen"]["FPS"].type() != Json::ValueType::nullValue ? c["screen"]["FPS"].asInt() : this->screen.FPS; this->tune.file = c["music"]["file"].type() != Json::ValueType::nullValue ? c["music"]["file"].asString() : this->tune.file; - this->tune.BPM = c["music"]["BPM"].type() != Json::ValueType::nullValue ? c["music"]["BPM"].asInt() : this->tune.BPM; + this->tune.BPM = c["music"]["BPM"].type() != Json::ValueType::nullValue ? c["music"]["BPM"].asDouble() : this->tune.BPM; + this->tune.RPB = c["music"]["RPB"].type() != Json::ValueType::nullValue ? c["music"]["RPB"].asInt() : this->tune.RPB; this->tune.frequency = c["music"]["frequency"].type() != Json::ValueType::nullValue ? c["music"]["frequency"].asInt() : this->tune.frequency; this->shaders.vertex = c["shaders"]["vertex"].type() != Json::ValueType::nullValue ? c["shaders"]["vertex"].asString() : this->shaders.vertex; diff --git a/libs/Cosmonaut.cpp b/libs/Cosmonaut.cpp new file mode 100644 index 0000000..08f27c5 --- /dev/null +++ b/libs/Cosmonaut.cpp @@ -0,0 +1,25 @@ +#include "Cosmonaut.h" + +DemoSystem::Cosmonaut::Cosmonaut() { + this->player = false; + this->BPM = 120.0; + this->RPB = 8; +} + +DemoSystem::Cosmonaut::~Cosmonaut() { + sync_destroy_device(this->device); +} + +void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { + this->player = player; + this->BPM = bpm; + this->RPB = rpb; + this->rowRate = this->BPM / 60.0 * this->RPB; + + this->device = sync_create_device("sync"); +} + +bool DemoSystem::Cosmonaut::connectPlayer(std::string host) { + this->player = true; + sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); +} \ No newline at end of file diff --git a/libs/base.h b/libs/base.h new file mode 100644 index 0000000..44683c9 --- /dev/null +++ b/libs/base.h @@ -0,0 +1,35 @@ +#ifndef SYNC_BASE_H +#define SYNC_BASE_H + +#ifdef _MSC_VER + #define _CRT_SECURE_NO_WARNINGS 1 + #define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif + +#include + +/* configure inline keyword */ +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)) && !defined(__cplusplus) + #if defined(_MSC_VER) || defined(__GNUC__) || defined(__SASC) + #define inline __inline + #else + /* compiler does not support inline */ + #define inline + #endif +#endif + +/* configure lacking CRT features */ +#ifdef _MSC_VER + #if _MSC_VER < 1900 + #define snprintf _snprintf + #endif + /* int is 32-bit for both x86 and x64 */ + typedef unsigned int uint32_t; + #define UINT32_MAX UINT_MAX +#elif defined(__GNUC__) + #include +#elif defined(M68000) + typedef unsigned int uint32_t; +#endif + +#endif /* SYNC_BASE_H */ diff --git a/libs/device.c b/libs/device.c new file mode 100644 index 0000000..fda3cfa --- /dev/null +++ b/libs/device.c @@ -0,0 +1,599 @@ +#include "device.h" +#include "track.h" +#include +#include +#include +#include +#include + +#include + +#ifdef WIN32 +#include +#define S_ISDIR(m) (((m)& S_IFMT) == S_IFDIR) +#define mkdir(pathname, mode) _mkdir(pathname) +#endif + +static int find_track(struct sync_device *d, const char *name) +{ + int i; + for (i = 0; i < (int)d->num_tracks; ++i) + if (!strcmp(name, d->tracks[i]->name)) + return i; + return -1; /* not found */ +} + +static int valid_path_char(char ch) +{ + switch (ch) { + case '.': + case '_': + case '/': + return 1; + + default: + return isalnum(ch); + } +} + +static const char *path_encode(const char *path) +{ + static char temp[FILENAME_MAX]; + int i, pos = 0; + int path_len = (int)strlen(path); + for (i = 0; i < path_len; ++i) { + int ch = path[i]; + if (valid_path_char(ch)) { + if (pos >= sizeof(temp) - 1) + break; + + temp[pos++] = (char)ch; + } else { + if (pos >= sizeof(temp) - 3) + break; + + temp[pos++] = '-'; + temp[pos++] = "0123456789ABCDEF"[(ch >> 4) & 0xF]; + temp[pos++] = "0123456789ABCDEF"[ch & 0xF]; + } + } + + temp[pos] = '\0'; + return temp; +} + +static const char *sync_track_path(const char *base, const char *name) +{ + static char temp[FILENAME_MAX]; + strncpy(temp, base, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = '\0'; + strncat(temp, "_", sizeof(temp) - strlen(temp) - 1); + strncat(temp, path_encode(name), sizeof(temp) - strlen(temp) - 1); + strncat(temp, ".track", sizeof(temp) - strlen(temp) - 1); + return temp; +} + +#ifndef SYNC_PLAYER + +#define CLIENT_GREET "hello, synctracker!" +#define SERVER_GREET "hello, demo!" + +enum { + SET_KEY = 0, + DELETE_KEY = 1, + GET_TRACK = 2, + SET_ROW = 3, + PAUSE = 4, + SAVE_TRACKS = 5 +}; + +static inline int socket_poll(SOCKET socket) +{ + struct timeval to = { 0, 0 }; + fd_set fds; + + FD_ZERO(&fds); + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) +#endif + FD_SET(socket, &fds); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + return select((int)socket + 1, &fds, NULL, NULL, &to) > 0; +} + +static inline int xsend(SOCKET s, const void *buf, size_t len, int flags) +{ +#ifdef WIN32 + assert(len <= INT_MAX); + return send(s, (const char *)buf, (int)len, flags) != (int)len; +#else + return send(s, (const char *)buf, len, flags) != (int)len; +#endif +} + +static inline int xrecv(SOCKET s, void *buf, size_t len, int flags) +{ +#ifdef WIN32 + assert(len <= INT_MAX); + return recv(s, (char *)buf, (int)len, flags) != (int)len; +#else + return recv(s, (char *)buf, len, flags) != (int)len; +#endif +} + +#ifdef USE_AMITCP +static struct Library *socket_base = NULL; +#endif + +static SOCKET server_connect(const char *host, unsigned short nport) +{ + SOCKET sock = INVALID_SOCKET; +#ifdef USE_GETADDRINFO + struct addrinfo *addr, *curr; + char port[6]; +#else + struct hostent *he; + char **ap; +#endif + +#ifdef WIN32 + static int need_init = 1; + if (need_init) { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 0), &wsa)) + return INVALID_SOCKET; + need_init = 0; + } +#elif defined(USE_AMITCP) + if (!socket_base) { + socket_base = OpenLibrary("bsdsocket.library", 4); + if (!socket_base) + return INVALID_SOCKET; + } +#endif + +#ifdef USE_GETADDRINFO + + snprintf(port, sizeof(port), "%u", nport); + if (getaddrinfo(host, port, 0, &addr) != 0) + return INVALID_SOCKET; + + for (curr = addr; curr; curr = curr->ai_next) { + int family = curr->ai_family; + struct sockaddr *sa = curr->ai_addr; + int sa_len = (int)curr->ai_addrlen; + +#else + + he = gethostbyname(host); + if (!he) + return INVALID_SOCKET; + + for (ap = he->h_addr_list; *ap; ++ap) { + int family = he->h_addrtype; + struct sockaddr_in sin; + struct sockaddr *sa = (struct sockaddr *)&sin; + int sa_len = sizeof(*sa); + + sin.sin_family = he->h_addrtype; + sin.sin_port = htons(nport); + memcpy(&sin.sin_addr, *ap, he->h_length); + memset(&sin.sin_zero, 0, sizeof(sin.sin_zero)); + +#endif + + sock = socket(family, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) + continue; + + if (connect(sock, sa, sa_len) >= 0) { + char greet[128]; + + if (xsend(sock, CLIENT_GREET, strlen(CLIENT_GREET), 0) || + xrecv(sock, greet, strlen(SERVER_GREET), 0)) { + closesocket(sock); + sock = INVALID_SOCKET; + continue; + } + + if (!strncmp(SERVER_GREET, greet, strlen(SERVER_GREET))) + break; + } + + closesocket(sock); + sock = INVALID_SOCKET; + } + +#ifdef USE_GETADDRINFO + freeaddrinfo(addr); +#endif + + return sock; +} + +#else + +void sync_set_io_cb(struct sync_device *d, struct sync_io_cb *cb) +{ + d->io_cb.open = cb->open; + d->io_cb.read = cb->read; + d->io_cb.close = cb->close; +} + +#endif + +#ifdef NEED_STRDUP +static inline char *rocket_strdup(const char *str) +{ + char *ret = malloc(strlen(str) + 1); + if (ret) + strcpy(ret, str); + return ret; +} +#define strdup rocket_strdup +#endif + +struct sync_device *sync_create_device(const char *base) +{ + struct sync_device *d = malloc(sizeof(*d)); + if (!d) + return NULL; + + if (!base || base[0] == '/') + return NULL; + + d->base = strdup(path_encode(base)); + if (!d->base) { + free(d); + return NULL; + } + + d->tracks = NULL; + d->num_tracks = 0; + +#ifndef SYNC_PLAYER + d->row = -1; + d->sock = INVALID_SOCKET; +#endif + + d->io_cb.open = (void *(*)(const char *, const char *))fopen; + d->io_cb.read = (size_t (*)(void *, size_t, size_t, void *))fread; + d->io_cb.close = (int (*)(void *))fclose; + + return d; +} + +void sync_destroy_device(struct sync_device *d) +{ + int i; + +#ifndef SYNC_PLAYER + if (d->sock != INVALID_SOCKET) + closesocket(d->sock); +#endif + + for (i = 0; i < (int)d->num_tracks; ++i) { + free(d->tracks[i]->name); + free(d->tracks[i]->keys); + free(d->tracks[i]); + } + free(d->tracks); + free(d->base); + free(d); + +#if defined(USE_AMITCP) && !defined(SYNC_PLAYER) + if (socket_base) { + CloseLibrary(socket_base); + socket_base = NULL; + } +#endif +} + +static int read_track_data(struct sync_device *d, struct sync_track *t) +{ + int i; + void *fp = d->io_cb.open(sync_track_path(d->base, t->name), "rb"); + if (!fp) + return -1; + + d->io_cb.read(&t->num_keys, sizeof(int), 1, fp); + t->keys = malloc(sizeof(struct track_key) * t->num_keys); + if (!t->keys) + return -1; + + for (i = 0; i < (int)t->num_keys; ++i) { + struct track_key *key = t->keys + i; + char type; + d->io_cb.read(&key->row, sizeof(int), 1, fp); + d->io_cb.read(&key->value, sizeof(float), 1, fp); + d->io_cb.read(&type, sizeof(char), 1, fp); + key->type = (enum key_type)type; + } + + d->io_cb.close(fp); + return 0; +} + +static int create_leading_dirs(const char *path) +{ + char *pos, buf[FILENAME_MAX]; + + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + pos = buf; + + while (1) { + struct stat st; + + pos = strchr(pos, '/'); + if (!pos) + break; + *pos = '\0'; + + /* does path exist, but isn't a dir? */ + if (!stat(buf, &st)) { + if (!S_ISDIR(st.st_mode)) + return -1; + } else { + if (mkdir(buf, 0777)) + return -1; + } + + *pos++ = '/'; + } + + return 0; +} + +static int save_track(const struct sync_track *t, const char *path) +{ + int i; + FILE *fp; + + if (create_leading_dirs(path)) + return -1; + + fp = fopen(path, "wb"); + if (!fp) + return -1; + + fwrite(&t->num_keys, sizeof(int), 1, fp); + for (i = 0; i < (int)t->num_keys; ++i) { + char type = (char)t->keys[i].type; + fwrite(&t->keys[i].row, sizeof(int), 1, fp); + fwrite(&t->keys[i].value, sizeof(float), 1, fp); + fwrite(&type, sizeof(char), 1, fp); + } + + fclose(fp); + return 0; +} + +int sync_save_tracks(const struct sync_device *d) +{ + int i; + for (i = 0; i < (int)d->num_tracks; ++i) { + const struct sync_track *t = d->tracks[i]; + if (save_track(t, sync_track_path(d->base, t->name))) + return -1; + } + return 0; +} + +#ifndef SYNC_PLAYER + +static int fetch_track_data(struct sync_device *d, struct sync_track *t) +{ + unsigned char cmd = GET_TRACK; + uint32_t name_len; + + assert(strlen(t->name) <= UINT32_MAX); + name_len = htonl((uint32_t)strlen(t->name)); + + /* send request data */ + if (xsend(d->sock, (char *)&cmd, 1, 0) || + xsend(d->sock, (char *)&name_len, sizeof(name_len), 0) || + xsend(d->sock, t->name, (int)strlen(t->name), 0)) + { + closesocket(d->sock); + d->sock = INVALID_SOCKET; + return -1; + } + + return 0; +} + +static int handle_set_key_cmd(SOCKET sock, struct sync_device *data) +{ + uint32_t track, row; + union { + float f; + uint32_t i; + } v; + struct track_key key; + unsigned char type; + + if (xrecv(sock, (char *)&track, sizeof(track), 0) || + xrecv(sock, (char *)&row, sizeof(row), 0) || + xrecv(sock, (char *)&v.i, sizeof(v.i), 0) || + xrecv(sock, (char *)&type, 1, 0)) + return -1; + + track = ntohl(track); + v.i = ntohl(v.i); + + key.row = ntohl(row); + key.value = v.f; + + if (type >= KEY_TYPE_COUNT || track >= data->num_tracks) + return -1; + + key.type = (enum key_type)type; + return sync_set_key(data->tracks[track], &key); +} + +static int handle_del_key_cmd(SOCKET sock, struct sync_device *data) +{ + uint32_t track, row; + + if (xrecv(sock, (char *)&track, sizeof(track), 0) || + xrecv(sock, (char *)&row, sizeof(row), 0)) + return -1; + + track = ntohl(track); + row = ntohl(row); + + if (track >= data->num_tracks) + return -1; + + return sync_del_key(data->tracks[track], row); +} + +int sync_tcp_connect(struct sync_device *d, const char *host, unsigned short port) +{ + int i; + if (d->sock != INVALID_SOCKET) + closesocket(d->sock); + + d->sock = server_connect(host, port); + if (d->sock == INVALID_SOCKET) + return -1; + + for (i = 0; i < (int)d->num_tracks; ++i) { + free(d->tracks[i]->keys); + d->tracks[i]->keys = NULL; + d->tracks[i]->num_keys = 0; + } + + for (i = 0; i < (int)d->num_tracks; ++i) { + if (fetch_track_data(d, d->tracks[i])) { + closesocket(d->sock); + d->sock = INVALID_SOCKET; + return -1; + } + } + return 0; +} + +int sync_connect(struct sync_device *d, const char *host, unsigned short port) +{ + return sync_tcp_connect(d, host, port); +} + +int sync_update(struct sync_device *d, int row, struct sync_cb *cb, + void *cb_param) +{ + if (d->sock == INVALID_SOCKET) + return -1; + + /* look for new commands */ + while (socket_poll(d->sock)) { + unsigned char cmd = 0, flag; + uint32_t new_row; + if (xrecv(d->sock, (char *)&cmd, 1, 0)) + goto sockerr; + + switch (cmd) { + case SET_KEY: + if (handle_set_key_cmd(d->sock, d)) + goto sockerr; + break; + case DELETE_KEY: + if (handle_del_key_cmd(d->sock, d)) + goto sockerr; + break; + case SET_ROW: + if (xrecv(d->sock, (char *)&new_row, sizeof(new_row), 0)) + goto sockerr; + if (cb && cb->set_row) + cb->set_row(cb_param, ntohl(new_row)); + break; + case PAUSE: + if (xrecv(d->sock, (char *)&flag, 1, 0)) + goto sockerr; + if (cb && cb->pause) + cb->pause(cb_param, flag); + break; + case SAVE_TRACKS: + sync_save_tracks(d); + break; + default: + fprintf(stderr, "unknown cmd: %02x\n", cmd); + goto sockerr; + } + } + + if (cb && cb->is_playing && cb->is_playing(cb_param)) { + if (d->row != row && d->sock != INVALID_SOCKET) { + unsigned char cmd = SET_ROW; + uint32_t nrow = htonl(row); + if (xsend(d->sock, (char*)&cmd, 1, 0) || + xsend(d->sock, (char*)&nrow, sizeof(nrow), 0)) + goto sockerr; + d->row = row; + } + } + return 0; + +sockerr: + closesocket(d->sock); + d->sock = INVALID_SOCKET; + return -1; +} + +#endif /* !defined(SYNC_PLAYER) */ + +static int create_track(struct sync_device *d, const char *name) +{ + void *tmp; + struct sync_track *t; + assert(find_track(d, name) < 0); + + t = malloc(sizeof(*t)); + if (!t) + return -1; + + t->name = strdup(name); + t->keys = NULL; + t->num_keys = 0; + + tmp = realloc(d->tracks, sizeof(d->tracks[0]) * (d->num_tracks + 1)); + if (!tmp) { + free(t); + return -1; + } + + d->tracks = tmp; + d->tracks[d->num_tracks++] = t; + + return (int)d->num_tracks - 1; +} + +const struct sync_track *sync_get_track(struct sync_device *d, + const char *name) +{ + struct sync_track *t; + int idx = find_track(d, name); + if (idx >= 0) + return d->tracks[idx]; + + idx = create_track(d, name); + if (idx < 0) + return NULL; + + t = d->tracks[idx]; + +#ifndef SYNC_PLAYER + if (d->sock != INVALID_SOCKET) + fetch_track_data(d, t); + else +#endif + read_track_data(d, t); + + return t; +} diff --git a/libs/device.h b/libs/device.h new file mode 100644 index 0000000..93b9802 --- /dev/null +++ b/libs/device.h @@ -0,0 +1,54 @@ +#ifndef SYNC_DEVICE_H +#define SYNC_DEVICE_H + +#include "base.h" +#include "sync.h" + +#ifndef SYNC_PLAYER + +/* configure socket-stack */ +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #define USE_GETADDRINFO + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + #include +#elif defined(USE_AMITCP) + #include + #include + #include + #include + #define SOCKET int + #define INVALID_SOCKET -1 + #define select(n,r,w,e,t) WaitSelect(n,r,w,e,t,0) + #define closesocket(x) CloseSocket(x) +#else + #include + #include + #include + #include + #include + #define SOCKET int + #define INVALID_SOCKET -1 + #define closesocket(x) close(x) +#endif + +#endif /* !defined(SYNC_PLAYER) */ + +struct sync_device { + char *base; + struct sync_track **tracks; + size_t num_tracks; + +#ifndef SYNC_PLAYER + int row; + SOCKET sock; +#endif + struct sync_io_cb io_cb; +}; + +#endif /* SYNC_DEVICE_H */ diff --git a/libs/sync.h b/libs/sync.h new file mode 100644 index 0000000..e4e68a7 --- /dev/null +++ b/libs/sync.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2010 Contributors + * For conditions of distribution and use, see copyright notice in COPYING + */ + +#ifndef SYNC_H +#define SYNC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef __GNUC__ +#define SYNC_DEPRECATED(msg) __attribute__ ((deprecated(msg))) +#elif defined(_MSC_VER) +#define SYNC_DEPRECATED(msg) __declspec(deprecated("is deprecated: " msg)) +#else +#define SYNC_DEPRECATED(msg) +#endif + +struct sync_device; +struct sync_track; + +struct sync_device *sync_create_device(const char *); +void sync_destroy_device(struct sync_device *); + +#ifndef SYNC_PLAYER +struct sync_cb { + void (*pause)(void *, int); + void (*set_row)(void *, int); + int (*is_playing)(void *); +}; +#define SYNC_DEFAULT_PORT 1338 +int sync_tcp_connect(struct sync_device *, const char *, unsigned short); +int SYNC_DEPRECATED("use sync_tcp_connect instead") sync_connect(struct sync_device *, const char *, unsigned short); +int sync_update(struct sync_device *, int, struct sync_cb *, void *); +int sync_save_tracks(const struct sync_device *); +#endif /* defined(SYNC_PLAYER) */ + +struct sync_io_cb { + void *(*open)(const char *filename, const char *mode); + size_t (*read)(void *ptr, size_t size, size_t nitems, void *stream); + int (*close)(void *stream); +}; +void sync_set_io_cb(struct sync_device *d, struct sync_io_cb *cb); + +const struct sync_track *sync_get_track(struct sync_device *, const char *); +double sync_get_val(const struct sync_track *, double); + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(SYNC_H) */ diff --git a/libs/track.c b/libs/track.c new file mode 100644 index 0000000..e0bbe9c --- /dev/null +++ b/libs/track.c @@ -0,0 +1,121 @@ +#include +#include +#include + +#include "sync.h" +#include "track.h" +#include "base.h" + +static double key_linear(const struct track_key k[2], double row) +{ + double t = (row - k[0].row) / (k[1].row - k[0].row); + return k[0].value + (k[1].value - k[0].value) * t; +} + +static double key_smooth(const struct track_key k[2], double row) +{ + double t = (row - k[0].row) / (k[1].row - k[0].row); + t = t * t * (3 - 2 * t); + return k[0].value + (k[1].value - k[0].value) * t; +} + +static double key_ramp(const struct track_key k[2], double row) +{ + double t = (row - k[0].row) / (k[1].row - k[0].row); + t = pow(t, 2.0); + return k[0].value + (k[1].value - k[0].value) * t; +} + +double sync_get_val(const struct sync_track *t, double row) +{ + int idx, irow; + + /* If we have no keys at all, return a constant 0 */ + if (!t->num_keys) + return 0.0f; + + irow = (int)floor(row); + idx = key_idx_floor(t, irow); + + /* at the edges, return the first/last value */ + if (idx < 0) + return t->keys[0].value; + if (idx > (int)t->num_keys - 2) + return t->keys[t->num_keys - 1].value; + + /* interpolate according to key-type */ + switch (t->keys[idx].type) { + case KEY_STEP: + return t->keys[idx].value; + case KEY_LINEAR: + return key_linear(t->keys + idx, row); + case KEY_SMOOTH: + return key_smooth(t->keys + idx, row); + case KEY_RAMP: + return key_ramp(t->keys + idx, row); + default: + assert(0); + return 0.0f; + } +} + +int sync_find_key(const struct sync_track *t, int row) +{ + int lo = 0, hi = t->num_keys; + + /* binary search, t->keys is sorted by row */ + while (lo < hi) { + int mi = (lo + hi) / 2; + assert(mi != hi); + + if (t->keys[mi].row < row) + lo = mi + 1; + else if (t->keys[mi].row > row) + hi = mi; + else + return mi; /* exact hit */ + } + assert(lo == hi); + + /* return first key after row, negated and biased (to allow -0) */ + return -lo - 1; +} + +#ifndef SYNC_PLAYER +int sync_set_key(struct sync_track *t, const struct track_key *k) +{ + int idx = sync_find_key(t, k->row); + if (idx < 0) { + /* no exact hit, we need to allocate a new key */ + void *tmp; + idx = -idx - 1; + tmp = realloc(t->keys, sizeof(struct track_key) * + (t->num_keys + 1)); + if (!tmp) + return -1; + t->num_keys++; + t->keys = tmp; + memmove(t->keys + idx + 1, t->keys + idx, + sizeof(struct track_key) * (t->num_keys - idx - 1)); + } + t->keys[idx] = *k; + return 0; +} + +int sync_del_key(struct sync_track *t, int pos) +{ + void *tmp; + int idx = sync_find_key(t, pos); + assert(idx >= 0); + memmove(t->keys + idx, t->keys + idx + 1, + sizeof(struct track_key) * (t->num_keys - idx - 1)); + assert(t->keys); + tmp = realloc(t->keys, sizeof(struct track_key) * + (t->num_keys - 1)); + if (t->num_keys != 1 && !tmp) + return -1; + t->num_keys--; + t->keys = tmp; + return 0; +} +#endif diff --git a/libs/track.h b/libs/track.h new file mode 100644 index 0000000..fd59ded --- /dev/null +++ b/libs/track.h @@ -0,0 +1,47 @@ +#ifndef SYNC_TRACK_H +#define SYNC_TRACK_H + +#include +#include +#include "base.h" + +enum key_type { + KEY_STEP, /* stay constant */ + KEY_LINEAR, /* lerp to the next value */ + KEY_SMOOTH, /* smooth curve to the next value */ + KEY_RAMP, + KEY_TYPE_COUNT +}; + +struct track_key { + int row; + float value; + enum key_type type; +}; + +struct sync_track { + char *name; + struct track_key *keys; + int num_keys; +}; + +int sync_find_key(const struct sync_track *, int); +static inline int key_idx_floor(const struct sync_track *t, int row) +{ + int idx = sync_find_key(t, row); + if (idx < 0) + idx = -idx - 2; + return idx; +} + +#ifndef SYNC_PLAYER +int sync_set_key(struct sync_track *, const struct track_key *); +int sync_del_key(struct sync_track *, int); +static inline int is_key_frame(const struct sync_track *t, int row) +{ + return sync_find_key(t, row) >= 0; +} + +#endif /* !defined(SYNC_PLAYER) */ + +#endif /* SYNC_TRACK_H */ From 4992625667df16819a84fdc3a48cf92f3057472b Mon Sep 17 00:00:00 2001 From: helgrima Date: Sun, 9 Dec 2018 20:31:38 +0200 Subject: [PATCH 2/7] callback functions for rocket --- .vscode/tasks.json | 6 ++++-- configuration.json | 6 +++++- include/Configuration.h | 7 +++++++ include/Cosmonaut.h | 10 +++++++--- include/Music.h | 3 ++- src/Configuration.cpp | 10 ++++++++-- {libs => src}/Cosmonaut.cpp | 16 +++++++++++++++- src/Music.cpp | 25 ++++++++++++++++--------- 8 files changed, 64 insertions(+), 19 deletions(-) rename {libs => src}/Cosmonaut.cpp (58%) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ba6cf0f..f884ebe 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -45,12 +45,13 @@ "command": "g++.exe", "args": [ "libs/device.c", - "libs/track.c", - "libs/Cosmonaut.cpp", + "libs/track.c", "libs/jsoncpp.cpp", + "src/Cosmonaut.cpp", "src/Configuration.cpp", "src/Music.cpp", "main.cpp", + "-D_WIN32_WINNT=0x501", //For getting rocket socket communication work on windows "-w", "-g", "-ggdb", @@ -62,6 +63,7 @@ "-lfreeglut", "-lopengl32", "-lbass", + "-lws2_32", "-ID:\\development\\glew-2.1.0\\include", "-ID:\\development\\freeglut\\include", "-I${workspaceFolder}\\libs", diff --git a/configuration.json b/configuration.json index 51aa20e..62cb1bf 100644 --- a/configuration.json +++ b/configuration.json @@ -11,9 +11,13 @@ "music": { "file": "assets\\music\\demo-release.mp3", "BPM": 120.0, - "RPB": 8, "frequency": 44000 }, + "sync": { + "RPB": 8, + "file": "assets\\music\\demo-release.rocket", + "host": "localhost" + }, "shaders": { "vertex": "shaders\\vertex.glsl", "fragment": "shaders\\fragment.glsl" diff --git a/include/Configuration.h b/include/Configuration.h index 060850e..2e45607 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -23,6 +23,12 @@ namespace DemoSystem { int frequency; }; + struct Sync { + std::string file; + int RPB; + std::string host; + }; + struct Shaders { std::string vertex; std::string fragment; @@ -36,6 +42,7 @@ namespace DemoSystem { Demo demo; Screen screen; Tune tune; + Sync sync; Shaders shaders; }; diff --git a/include/Cosmonaut.h b/include/Cosmonaut.h index bbefbba..02b68a6 100644 --- a/include/Cosmonaut.h +++ b/include/Cosmonaut.h @@ -2,6 +2,8 @@ #define COSMONAUT_H #include #include +#include +#define SYNC_PLAYER namespace DemoSystem { class Cosmonaut { @@ -10,6 +12,9 @@ namespace DemoSystem { ~Cosmonaut(); void initialize(double bpm, int rpb); bool connectPlayer(std::string host); + void setFunctions(sync_cb* functions); + void update(double row); + double getRowRate(); private: bool player; @@ -19,10 +24,9 @@ namespace DemoSystem { double rowRate; sync_device* device; + sync_cb* functions; }; - - - } + #endif \ No newline at end of file diff --git a/include/Music.h b/include/Music.h index 8f89b46..ec51091 100644 --- a/include/Music.h +++ b/include/Music.h @@ -3,6 +3,7 @@ #include #include #include +#include "Cosmonaut.h" namespace DemoSystem { class Music { @@ -14,7 +15,7 @@ namespace DemoSystem { void pause(); bool isPlaying(); double position(); - void seek(double position); + void seek(double row); private: HSTREAM stream; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 552ab08..416857f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -11,9 +11,12 @@ DemoSystem::Configuration::Configuration() { this->tune.file = "tune.mp3"; this->tune.BPM = 80.0; - this->tune.RPB = 8; this->tune.frequency = 44000; + this->sync.RPB = 8; + this->sync.file = "tune.rocket"; + this->sync.host = "localhost"; + this->shaders.vertex = "shaders/vertex.glsl"; this->shaders.fragment = "shaders/fragment.glsl"; } @@ -35,9 +38,12 @@ bool DemoSystem::Configuration::read(std::string file) { this->tune.file = c["music"]["file"].type() != Json::ValueType::nullValue ? c["music"]["file"].asString() : this->tune.file; this->tune.BPM = c["music"]["BPM"].type() != Json::ValueType::nullValue ? c["music"]["BPM"].asDouble() : this->tune.BPM; - this->tune.RPB = c["music"]["RPB"].type() != Json::ValueType::nullValue ? c["music"]["RPB"].asInt() : this->tune.RPB; this->tune.frequency = c["music"]["frequency"].type() != Json::ValueType::nullValue ? c["music"]["frequency"].asInt() : this->tune.frequency; + this->sync.file = c["sync"]["file"].type() != Json::ValueType::nullValue ? c["sync"]["file"].asString() : this->sync.file; + this->sync.host = c["sync"]["host"].type() != Json::ValueType::nullValue ? c["sync"]["host"].asString() : this->sync.host; + this->sync.RPB = c["sync"]["RPB"].type() != Json::ValueType::nullValue ? c["sync"]["RPB"].asInt() : this->sync.RPB; + this->shaders.vertex = c["shaders"]["vertex"].type() != Json::ValueType::nullValue ? c["shaders"]["vertex"].asString() : this->shaders.vertex; this->shaders.fragment = c["shaders"]["fragment"].type() != Json::ValueType::nullValue ? c["shaders"]["fragment"].asString() : this->shaders.fragment; return true; diff --git a/libs/Cosmonaut.cpp b/src/Cosmonaut.cpp similarity index 58% rename from libs/Cosmonaut.cpp rename to src/Cosmonaut.cpp index 08f27c5..1ed7282 100644 --- a/libs/Cosmonaut.cpp +++ b/src/Cosmonaut.cpp @@ -11,7 +11,7 @@ DemoSystem::Cosmonaut::~Cosmonaut() { } void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { - this->player = player; + this->player = false; this->BPM = bpm; this->RPB = rpb; this->rowRate = this->BPM / 60.0 * this->RPB; @@ -22,4 +22,18 @@ void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { bool DemoSystem::Cosmonaut::connectPlayer(std::string host) { this->player = true; sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); +} + +void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { + this->functions = functions; +} + +void DemoSystem::Cosmonaut::update(double row) { + if(sync_update(this->device, (int)floor(row), this->functions, (void*)this)) { + sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); + } +} + +double DemoSystem::Cosmonaut::getRowRate() { + return this->rowRate; } \ No newline at end of file diff --git a/src/Music.cpp b/src/Music.cpp index f7bdf68..9b9f015 100644 --- a/src/Music.cpp +++ b/src/Music.cpp @@ -20,21 +20,29 @@ bool DemoSystem::Music::initialize(int frequency, std::string file) { } return true; - } void DemoSystem::Music::play() { - BASS_ChannelPlay(this->stream, false); + if(this->playing == false) { + BASS_ChannelPlay(this->stream, false); + } this->playing = true; } void DemoSystem::Music::pause() { - BASS_ChannelPause(this->stream); - this->playing = false; + if(this->playing == true) { + BASS_ChannelPause(this->stream); + this->playing = false; + } } bool DemoSystem::Music::isPlaying() { - return this->playing; + if(this->playing) { + return 1; + } + else { + return 0; + } } double DemoSystem::Music::position() { @@ -42,8 +50,7 @@ double DemoSystem::Music::position() { return BASS_ChannelBytes2Seconds(this->stream, bytePosition); } -void DemoSystem::Music::seek(double position) { - QWORD bytePosition = BASS_ChannelSeconds2Bytes(this->stream, position); - BASS_ChannelSetPosition(this->stream, bytePosition, -BASS_POS_BYTE); +void DemoSystem::Music::seek(double row) { + QWORD bytePosition = BASS_ChannelSeconds2Bytes(this->stream, row); + BASS_ChannelSetPosition(this->stream, bytePosition, BASS_POS_BYTE); } \ No newline at end of file From d709024dc66e4820a750e82381dc8e06ad81fe34 Mon Sep 17 00:00:00 2001 From: helgrima Date: Mon, 10 Dec 2018 19:09:31 +0200 Subject: [PATCH 3/7] moved code from cosmonaut destructor and separate function and unified music to do same --- include/Cosmonaut.h | 4 +++- include/Music.h | 1 + src/Cosmonaut.cpp | 12 +++++++++++- src/Music.cpp | 7 +++++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/include/Cosmonaut.h b/include/Cosmonaut.h index 02b68a6..151cd62 100644 --- a/include/Cosmonaut.h +++ b/include/Cosmonaut.h @@ -15,6 +15,7 @@ namespace DemoSystem { void setFunctions(sync_cb* functions); void update(double row); double getRowRate(); + void cleanUp(); private: bool player; @@ -22,9 +23,10 @@ namespace DemoSystem { double BPM; int RPB; double rowRate; - + sync_device* device; sync_cb* functions; + const sync_track *time; }; } diff --git a/include/Music.h b/include/Music.h index ec51091..3c85691 100644 --- a/include/Music.h +++ b/include/Music.h @@ -16,6 +16,7 @@ namespace DemoSystem { bool isPlaying(); double position(); void seek(double row); + void cleanUp(); private: HSTREAM stream; diff --git a/src/Cosmonaut.cpp b/src/Cosmonaut.cpp index 1ed7282..b11582d 100644 --- a/src/Cosmonaut.cpp +++ b/src/Cosmonaut.cpp @@ -1,4 +1,5 @@ #include "Cosmonaut.h" +#include DemoSystem::Cosmonaut::Cosmonaut() { this->player = false; @@ -7,7 +8,7 @@ DemoSystem::Cosmonaut::Cosmonaut() { } DemoSystem::Cosmonaut::~Cosmonaut() { - sync_destroy_device(this->device); + } void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { @@ -22,6 +23,7 @@ void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { bool DemoSystem::Cosmonaut::connectPlayer(std::string host) { this->player = true; sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); + this->time = sync_get_track(this->device, "time"); } void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { @@ -29,11 +31,19 @@ void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { } void DemoSystem::Cosmonaut::update(double row) { + //something destroys device in sync_update, ie calls destructor for cosmonaut if(sync_update(this->device, (int)floor(row), this->functions, (void*)this)) { sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); } + + double t = sync_get_val(this->time, row); + std::cout << "Time: " << t << std::endl; } double DemoSystem::Cosmonaut::getRowRate() { return this->rowRate; +} + +void DemoSystem::Cosmonaut::cleanUp() { + sync_destroy_device(this->device); } \ No newline at end of file diff --git a/src/Music.cpp b/src/Music.cpp index 9b9f015..2c60cd0 100644 --- a/src/Music.cpp +++ b/src/Music.cpp @@ -5,8 +5,6 @@ DemoSystem::Music::Music() { } DemoSystem::Music::~Music() { - BASS_SampleFree(this->stream); - BASS_Free(); } bool DemoSystem::Music::initialize(int frequency, std::string file) { @@ -53,4 +51,9 @@ double DemoSystem::Music::position() { void DemoSystem::Music::seek(double row) { QWORD bytePosition = BASS_ChannelSeconds2Bytes(this->stream, row); BASS_ChannelSetPosition(this->stream, bytePosition, BASS_POS_BYTE); +} + +void DemoSystem::Music::cleanUp() { + BASS_SampleFree(this->stream); + BASS_Free(); } \ No newline at end of file From d5aaf26d00581a31a5ad974d6a99804cb524b0b7 Mon Sep 17 00:00:00 2001 From: helgrima Date: Mon, 10 Dec 2018 19:10:21 +0200 Subject: [PATCH 4/7] callback functions for cosmonaut and exit from main loop --- main.cpp | 63 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/main.cpp b/main.cpp index 9688a2e..3f20d0a 100644 --- a/main.cpp +++ b/main.cpp @@ -11,10 +11,10 @@ #include #include "Configuration.h" #include "Music.h" +#include "Cosmonaut.h" const char* VERSION = "0.6"; - -bool handleConfigurations(std::string configurations); +#define SYNC_PLAYER bool initGL(); void update(); void render(); @@ -25,6 +25,10 @@ std::string readShaderSource(std::string path); bool compileShader(const GLenum type, const std::string source, bool first); void cleanUp(); +void musicPause(void* c, int flag); +void musicSetRow(void* c, int row); +int musicPlaying(void* c); + bool fullscreen; GLuint program; @@ -38,6 +42,7 @@ GLuint fragmentShader; DemoSystem::Configuration configurations; DemoSystem::Music music; +DemoSystem::Cosmonaut cosmonaut; int main(int argc, char* args[]) { @@ -63,8 +68,6 @@ int main(int argc, char* args[]) std::cerr << "[ERROR]: provide vertex and fragment shader files as parameter .ie -v vertex.glgl -f fragment.glsl" << std::endl; return 1; } - - music.initialize(configurations.tune.frequency, configurations.tune.file); glutInit(&argc, args); glutInitContextVersion(2, 1); @@ -72,6 +75,7 @@ int main(int argc, char* args[]) glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(configurations.screen.width, configurations.screen.height); glutCreateWindow((configurations.demo.group + " : " + configurations.demo.name).c_str()); + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); GLenum glewError = glewInit(); if (GLEW_OK != glewError) @@ -103,15 +107,42 @@ int main(int argc, char* args[]) glutKeyboardFunc(handleKeyboard); glutTimerFunc(1000 / configurations.screen.FPS, mainLoop, 0); + music.initialize(configurations.tune.frequency, configurations.tune.file); + cosmonaut.initialize(configurations.tune.BPM, configurations.sync.RPB); + cosmonaut.connectPlayer(configurations.sync.host); + sync_cb functions; + functions.is_playing = (void*)&musicPlaying; + functions.pause = (void*)&musicPause; + functions.set_row = (void*)&musicSetRow; + cosmonaut.setFunctions(&functions); + music.play(); glutMainLoop(); cleanUp(); return 0; } -bool handleConfigurations(std::string path) { +void musicPause(void* c, int flag) { + if(flag == 1) { + music.pause(); + } + else { + music.play(); + } +} - return true; +void musicSetRow(void* c, int row) { + DemoSystem::Cosmonaut c1 = *((DemoSystem::Cosmonaut *)c); + music.seek(row / c1.getRowRate()); +} + +int musicPlaying(void* c) { + if(music.isPlaying()) { + return 1; + } + else { + return 0; + } } bool initGL() { @@ -135,10 +166,10 @@ bool initGL() { void update() { - if(music.isPlaying()) { - std::cout << "Position: " << music.position() << std::endl; - } + double position = music.position(); + cosmonaut.update(position * cosmonaut.getRowRate()); } + void render() { glClear(GL_COLOR_BUFFER_BIT); @@ -176,13 +207,8 @@ void handleKeyboard(unsigned char key, int x, int y) case 'r': initShaders(false); break; - case 'p': - if(music.isPlaying()) { - music.pause(); - } - else { - music.play(); - } + case 27: + glutLeaveMainLoop(); break; } } @@ -302,8 +328,9 @@ bool compileShader(const GLenum type, std::string source, bool first) } void cleanUp() { - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); glDeleteProgram(program); program = 0; + + cosmonaut.cleanUp(); + music.cleanUp(); } \ No newline at end of file From 4745a6479cabb9f1f5bb08edd69ec189c543a3b0 Mon Sep 17 00:00:00 2001 From: helgrima Date: Wed, 12 Dec 2018 18:06:29 +0200 Subject: [PATCH 5/7] reading rocket track from configuration and first attempt to communicate rocket and shaders --- configuration.json | 11 ++++++- include/Configuration.h | 15 ++++++++++ include/Cosmonaut.h | 29 ++++++++++++++++++- main.cpp | 63 +++++++++++++++++++++++++++-------------- shaders/fragment.glsl | 3 +- src/Configuration.cpp | 19 +++++++++++++ src/Cosmonaut.cpp | 49 +++++++++++++++++++++++++++----- 7 files changed, 158 insertions(+), 31 deletions(-) diff --git a/configuration.json b/configuration.json index 62cb1bf..921b325 100644 --- a/configuration.json +++ b/configuration.json @@ -21,5 +21,14 @@ "shaders": { "vertex": "shaders\\vertex.glsl", "fragment": "shaders\\fragment.glsl" - } + }, + "tracks": [ + { + "type": "float3", + "name": { + "track": "scene1.color", + "variable": "baseColor" + } + } + ] } \ No newline at end of file diff --git a/include/Configuration.h b/include/Configuration.h index 2e45607..a55da56 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include "sync.h" namespace DemoSystem { struct Demo { @@ -34,6 +36,18 @@ namespace DemoSystem { std::string fragment; }; + struct Track { + enum TrackType { + FLOAT1, + FLOAT2, + FLOAT3 + }; + + TrackType type; + std::string trackName; + std::string variableName; + }; + class Configuration { public: Configuration(); @@ -44,6 +58,7 @@ namespace DemoSystem { Tune tune; Sync sync; Shaders shaders; + std::list tracks; }; } diff --git a/include/Cosmonaut.h b/include/Cosmonaut.h index 151cd62..e518b6a 100644 --- a/include/Cosmonaut.h +++ b/include/Cosmonaut.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include "Configuration.h" #define SYNC_PLAYER namespace DemoSystem { @@ -14,9 +17,32 @@ namespace DemoSystem { bool connectPlayer(std::string host); void setFunctions(sync_cb* functions); void update(double row); + void setTracks(std::list tracks); + double getRowRate(); void cleanUp(); + struct SyncTrack { + const sync_track* x; + const sync_track* y; + const sync_track* z; + }; + + struct Vector3 { + double x; + double y; + double z; + }; + + struct Gateway { + SyncTrack syncTrack; + Vector3 value; + DemoSystem::Track::TrackType type; + std::string name; + GLint uniform; + }; + + std::list getGateways(); private: bool player; std::string host; @@ -26,7 +52,8 @@ namespace DemoSystem { sync_device* device; sync_cb* functions; - const sync_track *time; + std::list gateways; + }; } diff --git a/main.cpp b/main.cpp index 3f20d0a..b661e71 100644 --- a/main.cpp +++ b/main.cpp @@ -16,8 +16,8 @@ const char* VERSION = "0.6"; #define SYNC_PLAYER bool initGL(); -void update(); -void render(); +void update(double time); +void render(double time); void handleKeyboard(unsigned char key, int x, int y); void mainLoop(int); bool initShaders(bool first); @@ -42,7 +42,7 @@ GLuint fragmentShader; DemoSystem::Configuration configurations; DemoSystem::Music music; -DemoSystem::Cosmonaut cosmonaut; +DemoSystem::Cosmonaut* cosmonaut; int main(int argc, char* args[]) { @@ -108,13 +108,22 @@ int main(int argc, char* args[]) glutTimerFunc(1000 / configurations.screen.FPS, mainLoop, 0); music.initialize(configurations.tune.frequency, configurations.tune.file); - cosmonaut.initialize(configurations.tune.BPM, configurations.sync.RPB); - cosmonaut.connectPlayer(configurations.sync.host); + cosmonaut = new DemoSystem::Cosmonaut(); + cosmonaut->initialize(configurations.tune.BPM, configurations.sync.RPB); + cosmonaut->connectPlayer(configurations.sync.host); sync_cb functions; functions.is_playing = (void*)&musicPlaying; functions.pause = (void*)&musicPause; functions.set_row = (void*)&musicSetRow; - cosmonaut.setFunctions(&functions); + cosmonaut->setFunctions(&functions); + cosmonaut->setTracks(configurations.tracks); + //This should be done inside of cosmonaut, but glGetUniformLocation is not workin + //Find a way to fix this + + for(DemoSystem::Cosmonaut::Gateway gateway : cosmonaut->getGateways()) { + gateway.uniform = glGetUniformLocation(program, gateway.name.c_str()); + } + music.play(); glutMainLoop(); @@ -122,7 +131,7 @@ int main(int argc, char* args[]) return 0; } -void musicPause(void* c, int flag) { +void musicPause(void* rr, int flag) { if(flag == 1) { music.pause(); } @@ -131,12 +140,12 @@ void musicPause(void* c, int flag) { } } -void musicSetRow(void* c, int row) { - DemoSystem::Cosmonaut c1 = *((DemoSystem::Cosmonaut *)c); - music.seek(row / c1.getRowRate()); +void musicSetRow(void* rr, int row) { + double rowRate = *((double *)rr); + music.seek(row / rowRate); } -int musicPlaying(void* c) { +int musicPlaying(void* rr) { if(music.isPlaying()) { return 1; } @@ -164,21 +173,31 @@ bool initGL() { } -void update() +void update(double time) { - double position = music.position(); - cosmonaut.update(position * cosmonaut.getRowRate()); + cosmonaut->update(time * cosmonaut->getRowRate()); } -void render() +void render(double time) { glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); - GLint time = glutGet(GLUT_ELAPSED_TIME); - glUniform1f(timeUniform, (GLfloat)time / 1000.0); + glUniform1f(timeUniform, (GLfloat)time); glUniform2f(resolutionUniform, (GLfloat)configurations.screen.width, (GLfloat)configurations.screen.height); - + //Somthing is reseting values, maybe gateway is destroyed? + for(DemoSystem::Cosmonaut::Gateway gateway : cosmonaut->getGateways()) { + if(gateway.type == DemoSystem::Track::FLOAT1) { + glUniform1f(gateway.uniform, (GLfloat)gateway.value.x); + } + else if(gateway.type == DemoSystem::Track::FLOAT2) { + glUniform2f(gateway.uniform, (GLfloat)gateway.value.x, (GLfloat)gateway.value.y); + } + else if(gateway.type == DemoSystem::Track::FLOAT3) { + glUniform3f(gateway.uniform, (GLfloat)gateway.value.x, (GLfloat)gateway.value.y, (GLfloat)gateway.value.z); + } + } + glBegin(GL_QUADS); glVertex2f(-1.0f, -1.0f); glVertex2f( 1.0f, -1.0f); @@ -214,8 +233,9 @@ void handleKeyboard(unsigned char key, int x, int y) } void mainLoop(int val) { - update(); - render(); + double position = music.position(); + update(position); + render(position); glutTimerFunc(1000 / configurations.screen.FPS, mainLoop, val); } @@ -331,6 +351,7 @@ void cleanUp() glDeleteProgram(program); program = 0; - cosmonaut.cleanUp(); + cosmonaut->cleanUp(); music.cleanUp(); + delete cosmonaut; } \ No newline at end of file diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl index 3085838..c223f56 100644 --- a/shaders/fragment.glsl +++ b/shaders/fragment.glsl @@ -2,6 +2,7 @@ uniform float time; uniform vec2 resolution; +uniform vec3 baseColor; void main() { vec2 position = (gl_FragCoord.xy / resolution.xy); float color = 0.0; @@ -9,5 +10,5 @@ void main() { color += sin(position.y * sin(time / 10.0) * 40.0) + cos(position.x * sin(time / 25.0) * 40.0); color += sin(position.x * sin(time / 5.0) * 10.0) + sin(position.y * sin(time / 35.0) * 80.0); color *= sin(time / 10.0) * 0.5; - gl_FragColor = vec4(vec3(color, color * 0.5, sin(color + time / 3.0) * 0.75), 1.0); + gl_FragColor = vec4(vec3(color, color * 0.5, sin(color + time / 3.0) * 0.75) * baseColor, 1.0); } \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 416857f..7d9d0e6 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -46,6 +46,25 @@ bool DemoSystem::Configuration::read(std::string file) { this->shaders.vertex = c["shaders"]["vertex"].type() != Json::ValueType::nullValue ? c["shaders"]["vertex"].asString() : this->shaders.vertex; this->shaders.fragment = c["shaders"]["fragment"].type() != Json::ValueType::nullValue ? c["shaders"]["fragment"].asString() : this->shaders.fragment; + + if(c["tracks"].type() != Json::ValueType::nullValue) { + for(int i = 0; i < c["tracks"].size(); i++) { + Track t; + t.variableName = c["tracks"][i]["name"]["variable"].asString(); + t.trackName = c["tracks"][i]["name"]["track"].asString(); + std::string type = c["tracks"][i]["type"].asString(); + if(type == "float1") { + t.type = DemoSystem::Track::FLOAT1; + } + else if(type == "float2") { + t.type = DemoSystem::Track::FLOAT2; + } + else if(type == "float3") { + t.type = DemoSystem::Track::FLOAT3; + } + this->tracks.push_back(t); + } + } return true; } else { diff --git a/src/Cosmonaut.cpp b/src/Cosmonaut.cpp index b11582d..ee67e92 100644 --- a/src/Cosmonaut.cpp +++ b/src/Cosmonaut.cpp @@ -1,5 +1,4 @@ #include "Cosmonaut.h" -#include DemoSystem::Cosmonaut::Cosmonaut() { this->player = false; @@ -23,7 +22,6 @@ void DemoSystem::Cosmonaut::initialize(double bpm, int rpb) { bool DemoSystem::Cosmonaut::connectPlayer(std::string host) { this->player = true; sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); - this->time = sync_get_track(this->device, "time"); } void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { @@ -32,12 +30,45 @@ void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { void DemoSystem::Cosmonaut::update(double row) { //something destroys device in sync_update, ie calls destructor for cosmonaut - if(sync_update(this->device, (int)floor(row), this->functions, (void*)this)) { + if(sync_update(this->device, (int)floor(row), this->functions, &this->rowRate)) { sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); } - - double t = sync_get_val(this->time, row); - std::cout << "Time: " << t << std::endl; + + for(DemoSystem::Cosmonaut::Gateway gateway : this->gateways) { + if(gateway.type == DemoSystem::Track::FLOAT1) { + gateway.value.x = sync_get_val(gateway.syncTrack.x, row); + } + else if(gateway.type == DemoSystem::Track::FLOAT2) { + gateway.value.x = sync_get_val(gateway.syncTrack.x, row); + gateway.value.y = sync_get_val(gateway.syncTrack.y, row); + } + else if(gateway.type == DemoSystem::Track::FLOAT3) { + gateway.value.x = sync_get_val(gateway.syncTrack.x, row); + gateway.value.y = sync_get_val(gateway.syncTrack.y, row); + gateway.value.z = sync_get_val(gateway.syncTrack.z, row); + } + } +} + +void DemoSystem::Cosmonaut::setTracks(std::list tracks) { + for(DemoSystem::Track track : tracks) { + Gateway gateway; + gateway.type = track.type; + gateway.name = track.variableName; + if(track.type == DemoSystem::Track::FLOAT1) { + gateway.syncTrack.x = sync_get_track(this->device, track.trackName.c_str()); + } + else if(track.type == DemoSystem::Track::FLOAT2) { + gateway.syncTrack.x = sync_get_track(this->device, (track.trackName + ".x").c_str()); + gateway.syncTrack.y = sync_get_track(this->device, (track.trackName + ".y").c_str()); + } + else if(track.type == DemoSystem::Track::FLOAT3) { + gateway.syncTrack.x = sync_get_track(this->device, (track.trackName + ".x").c_str()); + gateway.syncTrack.y = sync_get_track(this->device, (track.trackName + ".y").c_str()); + gateway.syncTrack.z = sync_get_track(this->device, (track.trackName + ".z").c_str()); + } + this->gateways.push_back(gateway); + } } double DemoSystem::Cosmonaut::getRowRate() { @@ -46,4 +77,8 @@ double DemoSystem::Cosmonaut::getRowRate() { void DemoSystem::Cosmonaut::cleanUp() { sync_destroy_device(this->device); -} \ No newline at end of file +} + +std::list DemoSystem::Cosmonaut::getGateways() { + return this->gateways; +} From 12145732fffe706710ff6b9aa2a4842e87d44ff5 Mon Sep 17 00:00:00 2001 From: helgrima Date: Wed, 12 Dec 2018 21:42:44 +0200 Subject: [PATCH 6/7] working communication between rocket and shader --- include/Cosmonaut.h | 4 +-- main.cpp | 62 ++++++++++++++++++++++----------------------- src/Cosmonaut.cpp | 25 ++++++++---------- 3 files changed, 43 insertions(+), 48 deletions(-) diff --git a/include/Cosmonaut.h b/include/Cosmonaut.h index e518b6a..2d2fa8f 100644 --- a/include/Cosmonaut.h +++ b/include/Cosmonaut.h @@ -42,7 +42,7 @@ namespace DemoSystem { GLint uniform; }; - std::list getGateways(); + std::list gateways; private: bool player; std::string host; @@ -52,7 +52,7 @@ namespace DemoSystem { sync_device* device; sync_cb* functions; - std::list gateways; + }; } diff --git a/main.cpp b/main.cpp index b661e71..508ee6d 100644 --- a/main.cpp +++ b/main.cpp @@ -13,8 +13,9 @@ #include "Music.h" #include "Cosmonaut.h" -const char* VERSION = "0.6"; +const char* VERSION = "0.7"; #define SYNC_PLAYER + bool initGL(); void update(double time); void render(double time); @@ -42,7 +43,7 @@ GLuint fragmentShader; DemoSystem::Configuration configurations; DemoSystem::Music music; -DemoSystem::Cosmonaut* cosmonaut; +DemoSystem::Cosmonaut cosmonaut; int main(int argc, char* args[]) { @@ -91,6 +92,16 @@ int main(int argc, char* args[]) std::cout << "[INFO]: glew version: " << glewGetString(GLEW_VERSION) << std::endl; std::cout << "[INFO]: bass version: " << BASS_GetVersion() << std::endl; + music.initialize(configurations.tune.frequency, configurations.tune.file); + cosmonaut.initialize(configurations.tune.BPM, configurations.sync.RPB); + cosmonaut.connectPlayer(configurations.sync.host); + sync_cb functions; + functions.is_playing = (void*)&musicPlaying; + functions.pause = (void*)&musicPause; + functions.set_row = (void*)&musicSetRow; + cosmonaut.setFunctions(&functions); + cosmonaut.setTracks(configurations.tracks); + if(!initShaders(true)) { std::cerr << "[ERROR]: init shaders error" << std::endl; @@ -106,24 +117,8 @@ int main(int argc, char* args[]) glutDisplayFunc(render); glutKeyboardFunc(handleKeyboard); glutTimerFunc(1000 / configurations.screen.FPS, mainLoop, 0); - - music.initialize(configurations.tune.frequency, configurations.tune.file); - cosmonaut = new DemoSystem::Cosmonaut(); - cosmonaut->initialize(configurations.tune.BPM, configurations.sync.RPB); - cosmonaut->connectPlayer(configurations.sync.host); - sync_cb functions; - functions.is_playing = (void*)&musicPlaying; - functions.pause = (void*)&musicPause; - functions.set_row = (void*)&musicSetRow; - cosmonaut->setFunctions(&functions); - cosmonaut->setTracks(configurations.tracks); - //This should be done inside of cosmonaut, but glGetUniformLocation is not workin - //Find a way to fix this - - for(DemoSystem::Cosmonaut::Gateway gateway : cosmonaut->getGateways()) { - gateway.uniform = glGetUniformLocation(program, gateway.name.c_str()); - } + music.play(); glutMainLoop(); @@ -175,7 +170,7 @@ bool initGL() { void update(double time) { - cosmonaut->update(time * cosmonaut->getRowRate()); + cosmonaut.update(time * cosmonaut.getRowRate()); } void render(double time) @@ -185,19 +180,19 @@ void render(double time) glUniform1f(timeUniform, (GLfloat)time); glUniform2f(resolutionUniform, (GLfloat)configurations.screen.width, (GLfloat)configurations.screen.height); - //Somthing is reseting values, maybe gateway is destroyed? - for(DemoSystem::Cosmonaut::Gateway gateway : cosmonaut->getGateways()) { - if(gateway.type == DemoSystem::Track::FLOAT1) { - glUniform1f(gateway.uniform, (GLfloat)gateway.value.x); + + for(std::list::iterator it = cosmonaut.gateways.begin(); it != cosmonaut.gateways.end(); ++it) { + if(it->type == DemoSystem::Track::FLOAT1) { + glUniform1f(it->uniform, (GLfloat)it->value.x); } - else if(gateway.type == DemoSystem::Track::FLOAT2) { - glUniform2f(gateway.uniform, (GLfloat)gateway.value.x, (GLfloat)gateway.value.y); + else if(it->type == DemoSystem::Track::FLOAT2) { + glUniform2f(it->uniform, (GLfloat)it->value.x, (GLfloat)it->value.y); } - else if(gateway.type == DemoSystem::Track::FLOAT3) { - glUniform3f(gateway.uniform, (GLfloat)gateway.value.x, (GLfloat)gateway.value.y, (GLfloat)gateway.value.z); + else if(it->type == DemoSystem::Track::FLOAT3) { + glUniform3f(it->uniform, (GLfloat)it->value.x, (GLfloat)it->value.y, (GLfloat)it->value.z); } } - + glBegin(GL_QUADS); glVertex2f(-1.0f, -1.0f); glVertex2f( 1.0f, -1.0f); @@ -297,6 +292,12 @@ bool initShaders(bool first) } timeUniform = glGetUniformLocation(program, "time"); resolutionUniform = glGetUniformLocation(program, "resolution"); + + //This should be done inside of cosmonaut + //Find a way to fix this + for(std::list::iterator it = cosmonaut.gateways.begin(); it != cosmonaut.gateways.end(); ++it) { + it->uniform = glGetUniformLocation(program, it->name.c_str()); + } return true; } @@ -351,7 +352,6 @@ void cleanUp() glDeleteProgram(program); program = 0; - cosmonaut->cleanUp(); + cosmonaut.cleanUp(); music.cleanUp(); - delete cosmonaut; } \ No newline at end of file diff --git a/src/Cosmonaut.cpp b/src/Cosmonaut.cpp index ee67e92..dbc86ad 100644 --- a/src/Cosmonaut.cpp +++ b/src/Cosmonaut.cpp @@ -29,23 +29,22 @@ void DemoSystem::Cosmonaut::setFunctions(sync_cb* functions) { } void DemoSystem::Cosmonaut::update(double row) { - //something destroys device in sync_update, ie calls destructor for cosmonaut if(sync_update(this->device, (int)floor(row), this->functions, &this->rowRate)) { sync_tcp_connect(this->device, host.c_str(), SYNC_DEFAULT_PORT); } - for(DemoSystem::Cosmonaut::Gateway gateway : this->gateways) { - if(gateway.type == DemoSystem::Track::FLOAT1) { - gateway.value.x = sync_get_val(gateway.syncTrack.x, row); + for(std::list::iterator it = this->gateways.begin(); it != this->gateways.end(); ++it) { + if(it->type == DemoSystem::Track::FLOAT1) { + it->value.x = sync_get_val(it->syncTrack.x, row); } - else if(gateway.type == DemoSystem::Track::FLOAT2) { - gateway.value.x = sync_get_val(gateway.syncTrack.x, row); - gateway.value.y = sync_get_val(gateway.syncTrack.y, row); + else if(it->type == DemoSystem::Track::FLOAT2) { + it->value.x = sync_get_val(it->syncTrack.x, row); + it->value.y = sync_get_val(it->syncTrack.y, row); } - else if(gateway.type == DemoSystem::Track::FLOAT3) { - gateway.value.x = sync_get_val(gateway.syncTrack.x, row); - gateway.value.y = sync_get_val(gateway.syncTrack.y, row); - gateway.value.z = sync_get_val(gateway.syncTrack.z, row); + else if(it->type == DemoSystem::Track::FLOAT3) { + it->value.x = sync_get_val(it->syncTrack.x, row); + it->value.y = sync_get_val(it->syncTrack.y, row); + it->value.z = sync_get_val(it->syncTrack.z, row); } } } @@ -78,7 +77,3 @@ double DemoSystem::Cosmonaut::getRowRate() { void DemoSystem::Cosmonaut::cleanUp() { sync_destroy_device(this->device); } - -std::list DemoSystem::Cosmonaut::getGateways() { - return this->gateways; -} From 25dc67c0ecf2254af50fa20dc888e2804df35e1b Mon Sep 17 00:00:00 2001 From: helgrima Date: Wed, 12 Dec 2018 21:43:48 +0200 Subject: [PATCH 7/7] example music --- assets/music/demo-release.mp3 | Bin 0 -> 6542176 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/music/demo-release.mp3 diff --git a/assets/music/demo-release.mp3 b/assets/music/demo-release.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7b5ae8667fe74b749d65ed2d88cc4445da73a4e9 GIT binary patch literal 6542176 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