diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fabaea..6393b87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(projectM_Emscripten) message(STATUS "${CMAKE_PREFIX_PATH}") -find_package(libprojectM REQUIRED CONFIG) +find_package(projectM4 REQUIRED CONFIG COMPONENTS Playlist) # Setting the suffix to .html also creates an HTML file in addition to the .js and .wasm outputs. set(CMAKE_EXECUTABLE_SUFFIX ".html") @@ -15,7 +15,7 @@ add_executable(projectM_SDL_emscripten target_link_libraries(projectM_SDL_emscripten PRIVATE - libprojectM::static + libprojectM::playlist ) target_compile_options(projectM_SDL_emscripten @@ -29,6 +29,7 @@ target_link_options(projectM_SDL_emscripten "SHELL:-s MIN_WEBGL_VERSION=2" "SHELL:-s MAX_WEBGL_VERSION=2" "SHELL:-s FULL_ES2=1" + "SHELL:-s FULL_ES3=1" "SHELL:-s ALLOW_MEMORY_GROWTH=1" "SHELL:--preload-file ${PRESETS_DIR}@/presets" ) diff --git a/projectM_SDL_emscripten.cpp b/projectM_SDL_emscripten.cpp index 0fd64a1..b86220c 100755 --- a/projectM_SDL_emscripten.cpp +++ b/projectM_SDL_emscripten.cpp @@ -4,9 +4,11 @@ * */ -#include +#include +#include #include +#include #include @@ -14,34 +16,44 @@ #include #include +#include const int FPS = 60; +EM_JS(int, canvas_get_width, (),{ + return canvas.width; +}); + +EM_JS(int, canvas_get_height, (), { + return canvas.height; +}); + struct projectMApp { - projectm_handle pm{ nullptr }; + projectm_handle projectM{ nullptr }; + projectm_playlist_handle playlist{ nullptr }; SDL_Window* win{ nullptr }; - SDL_Renderer* rend{ nullptr }; - bool done{ false }; - projectm_settings settings; + SDL_Renderer* renderer{ nullptr }; SDL_AudioDeviceID audioInputDevice{ 0 }; int audioInputDevicesCount{ 0 }; int audioInputDeviceIndex{ 0 }; int audioChannelsCount{ 0 }; bool isFullscreen{ false }; + int width{}; + int height{}; }; -projectMApp app; +static projectMApp app; void audioInputCallbackF32(void* userdata, unsigned char* stream, int len) { if (app.audioChannelsCount == 1) { - projectm_pcm_add_float(app.pm, reinterpret_cast(stream), len / sizeof(float), PROJECTM_MONO); + projectm_pcm_add_float(app.projectM, reinterpret_cast(stream), len / sizeof(float), PROJECTM_MONO); } else if (app.audioChannelsCount == 2) { - projectm_pcm_add_float(app.pm, reinterpret_cast(stream), len / sizeof(float), PROJECTM_STEREO); + projectm_pcm_add_float(app.projectM, reinterpret_cast(stream), len / sizeof(float), PROJECTM_STEREO); } } @@ -96,7 +108,6 @@ bool selectAudioInput(projectMApp* application, int audioDeviceIndex) SDL_PauseAudioDevice(app.audioInputDevice, false); printf("Audio device specs: Channels=%d, Samplerate=%d, Format=%d\n", have.channels, have.freq, have.format); - projectm_set_toast_message(app.pm, audioDeviceName.c_str()); } else { @@ -106,11 +117,8 @@ bool selectAudioInput(projectMApp* application, int audioDeviceIndex) return true; } -void keyHandler(const SDL_Event& sdl_evt) +void keyHandler(projectMApp* appContext, const SDL_Event& sdl_evt) { - projectMEvent evt; - projectMKeycode key; - projectMModifier mod; auto sdl_mod = static_cast(sdl_evt.key.keysym.mod); SDL_Keycode sdl_keycode = sdl_evt.key.keysym.sym; bool keyMod = false; @@ -124,29 +132,40 @@ void keyHandler(const SDL_Event& sdl_evt) // handle keyboard input (for our app first, then projectM) switch (sdl_keycode) { + case SDLK_r: + { + bool shuffleMode = projectm_playlist_get_shuffle(appContext->playlist); + projectm_playlist_set_shuffle(appContext->playlist, true); + projectm_playlist_play_next(appContext->playlist, true); + projectm_playlist_set_shuffle(appContext->playlist, shuffleMode); + break; + } + + case SDLK_n: + projectm_playlist_play_next(appContext->playlist, true); + break; + + case SDLK_p: + projectm_playlist_play_previous(appContext->playlist, true); + break; + case SDLK_BACKSPACE: - projectm_delete_search_text(app.pm); + projectm_playlist_play_last(appContext->playlist, true); break; - case SDLK_RETURN: - if (!projectm_is_text_input_active(app.pm, false)) - { - SDL_StartTextInput(); - } + case SDLK_UP: + projectm_set_beat_sensitivity(appContext->projectM, projectm_get_beat_sensitivity(appContext->projectM) + 0.1f); break; - case SDLK_ESCAPE: - if (projectm_is_text_input_active(app.pm, false)) - { - SDL_StopTextInput(); - } + case SDLK_DOWN: + projectm_set_beat_sensitivity(appContext->projectM, projectm_get_beat_sensitivity(appContext->projectM) - 0.1f); break; case SDLK_f: if (keyMod) { - app.isFullscreen = !app.isFullscreen; - SDL_SetWindowFullscreen(app.win, app.isFullscreen ? SDL_WINDOW_FULLSCREEN : 0); + appContext->isFullscreen = !appContext->isFullscreen; + SDL_SetWindowFullscreen(appContext->win, appContext->isFullscreen ? SDL_WINDOW_FULLSCREEN : 0); return; // handled } break; @@ -154,135 +173,126 @@ void keyHandler(const SDL_Event& sdl_evt) case SDLK_i: if (keyMod) { - selectAudioInput(&app, app.audioInputDeviceIndex + 1); + selectAudioInput(appContext, appContext->audioInputDeviceIndex + 1); return; // handled } break; - - case SDLK_SPACE: - if (!projectm_is_text_input_active(app.pm, true)) - { - projectm_lock_preset(app.pm, !projectm_is_preset_locked(app.pm)); - } - break; - } - // translate into projectM codes and perform default projectM handler - /* - evt = sdl2pmEvent(&sdl_evt); - mod = sdl2pmModifier(sdl_mod); - key = sdl2pmKeycode(sdl_keycode, sdl_mod); - projectm_key_handler(app.pm, evt, key, mod); - */ +} + +void presetSwitchFailedEvent(const char* preset_filename, + const char* message, void* user_data) +{ + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Loading preset %s failed: %s.\n", preset_filename, message); } void presetSwitchedEvent(bool isHardCut, unsigned int index, void* context) { - auto presetName = projectm_get_preset_name(app.pm, index); + auto presetName = projectm_playlist_item(app.playlist, index); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Displaying preset: %s\n", presetName); std::string newTitle = "projectM ➫ " + std::string(presetName); - projectm_free_string(presetName); + projectm_playlist_free_string(presetName); SDL_SetWindowTitle(app.win, newTitle.c_str()); } -void generateRandomAudioData() +void renderFrame(void* appArg) { - short pcm_data[2][512]; + SDL_Event evt; - for (int i = 0; i < 512; i++) + int width = canvas_get_width(); + int height = canvas_get_height(); + + if (width != app.width || height != app.height) { - pcm_data[0][i] = static_cast((static_cast(rand()) / (static_cast(RAND_MAX)) * - (pow(2, 14)))); - pcm_data[1][i] = static_cast((static_cast(rand()) / (static_cast(RAND_MAX)) * - (pow(2, 14)))); + app.width = width; + app.height = height; - if (i % 2 == 1) - { - pcm_data[0][i] = -pcm_data[0][i]; - pcm_data[1][i] = -pcm_data[1][i]; - } + projectm_set_window_size(app.projectM, app.width, app.height); } - projectm_pcm_add_int16(app.pm, &pcm_data[0][0], 512, PROJECTM_STEREO); -} - -void renderFrame() -{ - SDL_Event evt; + auto* appContext = reinterpret_cast(appArg); SDL_PollEvent(&evt); switch (evt.type) { case SDL_KEYDOWN: - keyHandler(evt); - break; - - case SDL_TEXTINPUT: - if (projectm_is_text_input_active(app.pm, true)) - { - projectm_set_search_text(app.pm, evt.text.text); - projectm_populate_preset_menu(app.pm); - } + keyHandler(appContext, evt); break; case SDL_QUIT: - app.done = true; + emscripten_cancel_main_loop(); break; } - if (app.audioChannelsCount > 2 || app.audioChannelsCount < 1) - { - generateRandomAudioData(); - } - glClearColor(0.0, 0.5, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - projectm_render_frame(app.pm); + projectm_opengl_render_frame(appContext->projectM); glFlush(); - SDL_RenderPresent(app.rend); + SDL_RenderPresent(appContext->renderer); } int main(int argc, char* argv[]) { - int width = 1024; - int height = 1024; + app.width = 1920; + app.height = 1080; SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); - app.win = SDL_CreateWindow("projectM", 50, 50, width, height, 0); - app.rend = SDL_CreateRenderer(app.win, 0, SDL_RENDERER_ACCELERATED); - if (!app.rend) + SDL_CreateWindowAndRenderer(app.width, app.height, SDL_WINDOW_OPENGL, &app.win, &app.renderer); + if (!app.win || !app.renderer) + { + fprintf(stderr, "Failed to create SDL renderer: %s\n", SDL_GetError()); + return 1; + } + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL renderer initialized.\n"); + + // Enable floating-point texture support for motion vector grid. + auto webGlContext = emscripten_webgl_get_current_context(); + emscripten_webgl_enable_extension(webGlContext, "OES_texture_float"); + emscripten_webgl_enable_extension(webGlContext, "OES_texture_float_linear"); + + app.projectM = projectm_create(); + if (!app.projectM) { + fprintf(stderr, "Failed to create projectM instance!\n"); + return 1; + } + + app.playlist = projectm_playlist_create(app.projectM); + if (!app.playlist) { - fprintf(stderr, "Failed to create renderer: %s\n", SDL_GetError()); + fprintf(stderr, "Failed to create the playlist instance!\n"); return 1; } - app.settings.mesh_x = 48; - app.settings.mesh_y = 48; - app.settings.fps = FPS; - app.settings.texture_size = 1024; - app.settings.window_width = width; - app.settings.window_height = height; - app.settings.soft_cut_duration = 3; // seconds - app.settings.preset_duration = 30; //seconds - app.settings.beat_sensitivity = 0.9; - app.settings.aspect_correction = true; - app.settings.easter_egg = 0; // ??? - app.settings.shuffle_enabled = true; - app.settings.soft_cut_ratings_enabled = true; // ??? - app.settings.preset_url = projectm_alloc_string(8); - strncpy(app.settings.preset_url, "/presets", 8); - - // init projectM - app.pm = projectm_create_settings(&(app.settings), PROJECTM_FLAG_NONE); - projectm_select_random_preset(app.pm, true); - projectm_set_window_size(app.pm, width, height); - projectm_set_preset_switched_event_callback(app.pm, &presetSwitchedEvent, nullptr); + projectm_playlist_set_preset_switched_event_callback(app.playlist, &presetSwitchedEvent, nullptr); + projectm_set_preset_switch_failed_event_callback(app.projectM, &presetSwitchFailedEvent, nullptr); + + projectm_set_mesh_size(app.projectM, 48, 48); + projectm_set_fps(app.projectM, FPS); + projectm_set_window_size(app.projectM, app.width, app.height); + projectm_set_soft_cut_duration(app.projectM, 5.0); + projectm_set_preset_duration(app.projectM, 15); + projectm_set_beat_sensitivity(app.projectM, 1.0); + projectm_set_aspect_correction(app.projectM, true); + projectm_set_easter_egg(app.projectM, 0); + + std::string texturePath = "/textures"; + std::vector texturePathList; + texturePathList.push_back(texturePath.data()); + + projectm_set_texture_search_paths(app.projectM, texturePathList.data(), 1); + + projectm_playlist_set_shuffle(app.playlist, true); + projectm_playlist_add_path(app.playlist, "/presets", true, true); + + projectm_playlist_play_next(app.playlist, true); + printf("projectM initialized.\n"); // get an audio input device @@ -292,7 +302,7 @@ int main(int argc, char* argv[]) return 1; } - emscripten_set_main_loop(renderFrame, 0, 0); + emscripten_set_main_loop_arg(renderFrame, &app, 0, 1); return 0; }