diff --git a/common/common.cpp b/common/common.cpp new file mode 100644 index 0000000..21c44d5 --- /dev/null +++ b/common/common.cpp @@ -0,0 +1,76 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#include + +#include "common.hpp" + +namespace lsdj +{ + int handle_error(lsdj_error_t* error) + { + std::cerr << "ERROR: " << lsdj_error_get_c_str(error) << std::endl; + lsdj_error_free(error); + return 1; + } + + bool compareCaseInsensitive(std::string str1, std::string str2) + { + std::transform(str1.begin(), str1.end(), str1.begin(), ::tolower); + std::transform(str2.begin(), str2.end(), str2.begin(), ::tolower); + return str1 == str2; + } + + std::string constructProjectName(const lsdj_project_t* project, bool underscore) + { + char name[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + lsdj_project_get_name(project, name, sizeof(name)); + + if (underscore) + std::replace(name, name + 9, 'x', '_'); + + return name; + } + + bool isHiddenFile(const std::string& str) + { + switch (str.size()) + { + case 0: return true; + case 1: return false; + default: return str[0] == '.' && str[1] != '.' && str[1] != '/'; + } + } +} diff --git a/common/common.hpp b/common/common.hpp new file mode 100644 index 0000000..125b3f0 --- /dev/null +++ b/common/common.hpp @@ -0,0 +1,52 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#ifndef LSDJ_COMMON_HPP +#define LSDJ_COMMON_HPP + +#include + +#include "../liblsdj/error.h" +#include "../liblsdj/project.h" + +namespace lsdj +{ + int handle_error(lsdj_error_t* error); + bool compareCaseInsensitive(std::string str1, std::string str2); + std::string constructProjectName(const lsdj_project_t* project, bool underscore); + bool isHiddenFile(const std::string& str); +} + +#endif diff --git a/liblsdj/CMakeLists.txt b/liblsdj/CMakeLists.txt index aba5938..fbc488f 100644 --- a/liblsdj/CMakeLists.txt +++ b/liblsdj/CMakeLists.txt @@ -5,7 +5,7 @@ if (APPLE) add_definitions(-Wall -Werror -Wconversion -Wno-unused-variable) endif (APPLE) -set(HEADERS chain.h command.h compression.h error.h groove.h instrument.h instrument_constants.h instrument_kit.h instrument_noise.h instrument_pulse.h instrument_wave.h panning.h phrase.h project.h row.h sav.h song.h synth.h table.h vio.h wave.h word.h) +set(HEADERS chain.h channel.h command.h compression.h error.h groove.h instrument.h instrument_constants.h instrument_kit.h instrument_noise.h instrument_pulse.h instrument_wave.h panning.h phrase.h project.h row.h sav.h song.h synth.h table.h vio.h wave.h word.h) set(SOURCES chain.c command.c compression.c error.c groove.c instrument.c phrase.c project.c row.c sav.c song.c synth.c table.c vio.c wave.c word.c) # Create the library target @@ -14,4 +14,4 @@ set_target_properties(liblsdj PROPERTIES OUTPUT_NAME lsdj) source_group(\\ FILES ${HEADERS} ${SOURCES}) install(TARGETS liblsdj DESTINATION lib) -install(FILES command.h error.h instrument.h sav.h panning.h phrase.h project.h song.h table.h vio.h DESTINATION include/lsdj) +install(FILES chain.h channel.h command.h error.h groove.h instrument.h sav.h panning.h phrase.h project.h row.h song.h synth.h table.h wave.h word.h vio.h DESTINATION include/lsdj) diff --git a/liblsdj/chain.h b/liblsdj/chain.h index ee092e8..7041c7d 100644 --- a/liblsdj/chain.h +++ b/liblsdj/chain.h @@ -36,6 +36,10 @@ #ifndef LSDJ_CHAIN_H #define LSDJ_CHAIN_H +#ifdef __cplusplus +extern "C" { +#endif + #include "command.h" // The length of a chain @@ -56,5 +60,9 @@ lsdj_chain_t* lsdj_chain_copy(const lsdj_chain_t* chain); // Clear chain data to factory settings void lsdj_chain_clear(lsdj_chain_t* chain); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/channel.h b/liblsdj/channel.h new file mode 100644 index 0000000..7c51d89 --- /dev/null +++ b/liblsdj/channel.h @@ -0,0 +1,57 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#ifndef LSDJ_CHANNEL_H +#define LSDJ_CHANNEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + LSDJ_PULSE1, + LSDJ_PULSE2, + LSDJ_WAVE, + LSDJ_NOISE, +} lsdj_channel_t; + +#define LSDJ_CHANNEL_COUNT (4) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/liblsdj/command.h b/liblsdj/command.h index edada13..e49d382 100644 --- a/liblsdj/command.h +++ b/liblsdj/command.h @@ -33,14 +33,40 @@ */ -#ifndef LSDJ_COMMAND_H -#define LSDJ_COMMAND_H +#ifndef LSDJ_COMMAND_H_GUARD +#define LSDJ_COMMAND_H_GUARD + +#ifdef __cplusplus +extern "C" { +#endif #include #include -#define LSDJ_COMMAND_O (11) - +#define LSDJ_COMMAND_NONE (0x00) +#define LSDJ_COMMAND_A (0x01) +#define LSDJ_COMMAND_C (0x02) +#define LSDJ_COMMAND_D (0x03) +#define LSDJ_COMMAND_E (0x04) +#define LSDJ_COMMAND_F (0x05) +#define LSDJ_COMMAND_G (0x06) +#define LSDJ_COMMAND_H (0x07) +#define LSDJ_COMMAND_K (0x08) +#define LSDJ_COMMAND_L (0x09) +#define LSDJ_COMMAND_M (0x0a) +#define LSDJ_COMMAND_O (0x0b) +#define LSDJ_COMMAND_P (0x0c) +#define LSDJ_COMMAND_R (0x0d) +#define LSDJ_COMMAND_S (0x0e) +#define LSDJ_COMMAND_T (0x0f) +#define LSDJ_COMMAND_V (0x10) +#define LSDJ_COMMAND_W (0x11) +#define LSDJ_COMMAND_Z (0x12) +#define LSDJ_COMMAND_ARDUINO_BOY_N (0x13) +#define LSDJ_COMMAND_ARDUINO_BOY_X (0x14) +#define LSDJ_COMMAND_ARDUINO_BOY_Q (0x15) +#define LSDJ_COMMAND_ARDUINO_BOY_Y (0x16) + // Structure representing an effect command with its argument value typedef struct { @@ -50,5 +76,9 @@ typedef struct // Clear the command to factory settings void lsdj_command_clear(lsdj_command_t* command); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/compression.h b/liblsdj/compression.h index f42bc5b..f4b681c 100644 --- a/liblsdj/compression.h +++ b/liblsdj/compression.h @@ -38,7 +38,7 @@ #ifdef __cplusplus extern "C" { -#endif /* __cplusplus */ +#endif #include "error.h" #include "vio.h" @@ -54,6 +54,6 @@ unsigned int lsdj_compress_to_file(const unsigned char* data, unsigned int block #ifdef __cplusplus } -#endif /* __cplusplus */ +#endif #endif diff --git a/liblsdj/error.h b/liblsdj/error.h index 41f5c7b..1209b81 100644 --- a/liblsdj/error.h +++ b/liblsdj/error.h @@ -36,6 +36,10 @@ #ifndef LSDJ_ERROR_H #define LSDJ_ERROR_H +#ifdef __cplusplus +extern "C" { +#endif + // Structure containing specific error details typedef struct lsdj_error_t lsdj_error_t; @@ -49,5 +53,9 @@ void lsdj_error_free(lsdj_error_t* error); // Retrieve a string description of an error const char* lsdj_error_get_c_str(lsdj_error_t* error); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/groove.h b/liblsdj/groove.h index 0c5a6b1..f93192f 100644 --- a/liblsdj/groove.h +++ b/liblsdj/groove.h @@ -36,6 +36,10 @@ #ifndef LSDJ_GROOVE_H #define LSDJ_GROOVE_H +#ifdef __cplusplus +extern "C" { +#endif + // The default constant length of a groove #define LSDJ_GROOVE_LENGTH (16) @@ -48,4 +52,8 @@ typedef struct // Clear all groove data to factory settings void lsdj_groove_clear(lsdj_groove_t* groove); +#ifdef __cplusplus +} +#endif + #endif diff --git a/liblsdj/instrument.c b/liblsdj/instrument.c index 181e964..8089222 100644 --- a/liblsdj/instrument.c +++ b/liblsdj/instrument.c @@ -394,10 +394,11 @@ void read_kit_instrument(lsdj_vio_t* vio, unsigned char version, lsdj_instrument vio->seek(1, SEEK_CUR, vio->user_data); // Byte 4 is empty + // Byte 5 vio->read(&byte, 1, vio->user_data); if (instrument->kit.loop1 != LSDJ_KIT_LOOP_ATTACK) - instrument->kit.loop1 = ((byte >> 6) & 1) ? LSDJ_KIT_LOOP_ON : LSDJ_KIT_LOOP_OFF; - instrument->kit.loop2 = ((byte >> 6) & 1) ? LSDJ_KIT_LOOP_ON : LSDJ_KIT_LOOP_OFF; + instrument->kit.loop1 = (byte & 0x40) ? LSDJ_KIT_LOOP_ON : LSDJ_KIT_LOOP_OFF; + instrument->kit.loop2 = (byte & 0x20) ? LSDJ_KIT_LOOP_ON : LSDJ_KIT_LOOP_OFF; instrument->automate = parseAutomate(byte); instrument->kit.vibratoDirection = byte & 1; @@ -435,25 +436,30 @@ void read_kit_instrument(lsdj_vio_t* vio, unsigned char version, lsdj_instrument } } + // byte 6 vio->read(&byte, 1, vio->user_data); instrument->table = parseTable(byte); + // byte 7 vio->read(&byte, 1, vio->user_data); instrument->panning = parsePanning(byte); + // byte 8 vio->read(&instrument->kit.pitch, 1, vio->user_data); + // byte 9 vio->read(&byte, 1, vio->user_data); if ((byte >> 7) & 1) instrument->kit.loop2 = LSDJ_KIT_LOOP_ATTACK; instrument->kit.kit2 = byte & 0x3F; + // byte 10 vio->read(&byte, 1, vio->user_data); instrument->kit.distortion = parseKitDistortion(byte); - vio->read(&instrument->kit.length2, 1, vio->user_data); - vio->read(&instrument->kit.offset1, 1, vio->user_data); - vio->read(&instrument->kit.offset2, 1, vio->user_data); + vio->read(&instrument->kit.length2, 1, vio->user_data); // byte 11 + vio->read(&instrument->kit.offset1, 1, vio->user_data); // byte 12 + vio->read(&instrument->kit.offset2, 1, vio->user_data); // byte 13 vio->seek(2, SEEK_CUR, vio->user_data); // Bytes 14 and 15 are empty } @@ -720,19 +726,21 @@ void write_kit_instrument(const lsdj_instrument_t* instrument, unsigned char ver byte = 0xFF; vio->write(&byte, 1, vio->user_data); // Byte 4 is empty - byte = ((instrument->kit.loop1 == LSDJ_KIT_LOOP_ON) ? 0x80 : 0x0) | - ((instrument->kit.loop2 == LSDJ_KIT_LOOP_ON) ? 0x40 : 0x0) | + // byte 5 + byte = ((instrument->kit.loop1 == LSDJ_KIT_LOOP_ON) ? 0x40 : 0x0) | + ((instrument->kit.loop2 == LSDJ_KIT_LOOP_ON) ? 0x20 : 0x0) | createAutomateByte(instrument->automate); if (version < 4) { byte |= (instrument->kit.plvibSpeed & 3) << 1; } else { - byte |= (instrument->kit.vibShape & 3) << 1; - if (instrument->kit.plvibSpeed == LSDJ_PLVIB_TICK) - byte |= 0x10; - else if (instrument->kit.plvibSpeed == LSDJ_PLVIB_STEP) - byte |= 0x80; + switch (instrument->kit.plvibSpeed) + { + case LSDJ_PLVIB_FAST: break; + case LSDJ_PLVIB_TICK: byte |= 0x10; break; + case LSDJ_PLVIB_STEP: byte |= 0x80; break; + } } vio->write(&byte, 1, vio->user_data); @@ -819,8 +827,8 @@ void lsdj_instrument_get_name(const lsdj_instrument_t* instrument, char* data, s { const size_t len = strnlen(instrument->name, LSDJ_INSTRUMENT_NAME_LENGTH); strncpy(data, instrument->name, len); - if (len < size) - data[len] = '\0'; + for (size_t i = len; i < LSDJ_INSTRUMENT_NAME_LENGTH; i += 1) + data[i] = '\0'; } void lsdj_instrument_set_panning(lsdj_instrument_t* instrument, lsdj_panning panning) diff --git a/liblsdj/instrument.h b/liblsdj/instrument.h index 2e95bc5..f8bcf48 100644 --- a/liblsdj/instrument.h +++ b/liblsdj/instrument.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_H #define LSDJ_INSTRUMENT_H +#ifdef __cplusplus +extern "C" { +#endif + #include "error.h" #include "instrument_kit.h" #include "instrument_noise.h" @@ -85,5 +89,9 @@ void lsdj_instrument_get_name(const lsdj_instrument_t* instrument, char* data, s void lsdj_instrument_set_panning(lsdj_instrument_t* instrument, lsdj_panning panning); lsdj_panning lsdj_instrument_get_panning(const lsdj_instrument_t* instrument); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/instrument_constants.h b/liblsdj/instrument_constants.h index d75084e..cd95ca1 100644 --- a/liblsdj/instrument_constants.h +++ b/liblsdj/instrument_constants.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_CONSTANTS_H #define LSDJ_INSTRUMENT_CONSTANTS_H +#ifdef __cplusplus +extern "C" { +#endif + typedef unsigned char lsdj_plvib_speed; static const lsdj_plvib_speed LSDJ_PLVIB_FAST = 0; static const lsdj_plvib_speed LSDJ_PLVIB_TICK = 1; @@ -49,5 +53,9 @@ typedef unsigned char lsdj_vib_shape; typedef unsigned char lsdj_vib_direction; static const lsdj_vib_direction LSDJ_VIB_UP = 0; static const lsdj_vib_direction LSDJ_VIB_DOWN = 1; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/instrument_kit.h b/liblsdj/instrument_kit.h index f650845..8082969 100644 --- a/liblsdj/instrument_kit.h +++ b/liblsdj/instrument_kit.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_KIT_H #define LSDJ_INSTRUMENT_KIT_H +#ifdef __cplusplus +extern "C" { +#endif + #include "instrument_constants.h" typedef unsigned char lsdj_kit_loop_mode; @@ -73,5 +77,9 @@ typedef struct lsdj_vib_shape vibShape; lsdj_vib_direction vibratoDirection; } lsdj_instrument_kit_t; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/instrument_noise.h b/liblsdj/instrument_noise.h index 4569349..f6ca079 100644 --- a/liblsdj/instrument_noise.h +++ b/liblsdj/instrument_noise.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_NOISE_H #define LSDJ_INSTRUMENT_NOISE_H +#ifdef __cplusplus +extern "C" { +#endif + typedef unsigned char lsdj_scommand_type; static const lsdj_scommand_type LSDJ_SCOMMAND_FREE = 0; static const lsdj_scommand_type LSDJ_SCOMMAND_STABLE = 1; @@ -46,5 +50,9 @@ typedef struct unsigned char shape; lsdj_scommand_type sCommand; } lsdj_instrument_noise_t; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/instrument_pulse.h b/liblsdj/instrument_pulse.h index 7b5fdb2..c420474 100644 --- a/liblsdj/instrument_pulse.h +++ b/liblsdj/instrument_pulse.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_PULSE_H #define LSDJ_INSTRUMENT_PULSE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "instrument_constants.h" typedef unsigned char lsdj_pulse_wave; @@ -57,5 +61,9 @@ typedef struct unsigned char pulse2tune; unsigned char fineTune; } lsdj_instrument_pulse_t; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/instrument_wave.h b/liblsdj/instrument_wave.h index 28841f7..de53fdb 100644 --- a/liblsdj/instrument_wave.h +++ b/liblsdj/instrument_wave.h @@ -36,6 +36,10 @@ #ifndef LSDJ_INSTRUMENT_WAVE_H #define LSDJ_INSTRUMENT_WAVE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "instrument_constants.h" typedef unsigned char lsdj_playback_mode; @@ -57,5 +61,9 @@ typedef struct unsigned char repeat; unsigned char speed; } lsdj_instrument_wave_t; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/panning.h b/liblsdj/panning.h index 8a7c302..ac6c417 100644 --- a/liblsdj/panning.h +++ b/liblsdj/panning.h @@ -36,10 +36,18 @@ #ifndef LSDJ_PANNING_H #define LSDJ_PANNING_H +#ifdef __cplusplus +extern "C" { +#endif + typedef unsigned char lsdj_panning; static const lsdj_panning LSDJ_PAN_NONE = 0; static const lsdj_panning LSDJ_PAN_RIGHT = 1; static const lsdj_panning LSDJ_PAN_LEFT = 2; static const lsdj_panning LSDJ_PAN_LEFT_RIGHT = 3; + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/phrase.h b/liblsdj/phrase.h index a9e3a28..728923f 100644 --- a/liblsdj/phrase.h +++ b/liblsdj/phrase.h @@ -36,6 +36,10 @@ #ifndef LSDJ_PHRASE_H #define LSDJ_PHRASE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "command.h" // The default constant lenght of a phrase @@ -58,5 +62,9 @@ lsdj_phrase_t* lsdj_phrase_copy(const lsdj_phrase_t* phrase); // Clear all phrase data to factory settings void lsdj_phrase_clear(lsdj_phrase_t* phrase); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/project.c b/liblsdj/project.c index 20e0198..6bc46f5 100644 --- a/liblsdj/project.c +++ b/liblsdj/project.c @@ -273,8 +273,8 @@ void lsdj_project_get_name(const lsdj_project_t* project, char* data, size_t siz { const size_t len = strnlen(project->name, LSDJ_PROJECT_NAME_LENGTH); strncpy(data, project->name, len); - if (len < size) - data[len] = '\0'; + for (size_t i = len; i < LSDJ_PROJECT_NAME_LENGTH; i += 1) + data[i] = '\0'; } void lsdj_project_set_version(lsdj_project_t* project, unsigned char version) diff --git a/liblsdj/project.h b/liblsdj/project.h index bfc257c..2e9ec01 100644 --- a/liblsdj/project.h +++ b/liblsdj/project.h @@ -36,6 +36,10 @@ #ifndef LSDJ_PROJECT_H #define LSDJ_PROJECT_H +#ifdef __cplusplus +extern "C" { +#endif + #include #include "error.h" @@ -69,5 +73,9 @@ void lsdj_project_set_version(lsdj_project_t* project, unsigned char version); unsigned char lsdj_project_get_version(const lsdj_project_t* project); void lsdj_project_set_song(lsdj_project_t* project, lsdj_song_t* song); lsdj_song_t* lsdj_project_get_song(const lsdj_project_t* project); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/row.h b/liblsdj/row.h index 509083e..54ff89f 100644 --- a/liblsdj/row.h +++ b/liblsdj/row.h @@ -36,18 +36,29 @@ #ifndef LSDJ_ROW_H #define LSDJ_ROW_H +#include "channel.h" + +#ifdef __cplusplus +extern "C" { +#endif + // Structure representing a row in an LSDJ song sequence -typedef struct +typedef union { // Four chain indices /* The chains themselves are stored in the encompassing song */ - unsigned char pulse1; - unsigned char pulse2; - unsigned char wave; - unsigned char noise; + struct + { + unsigned char pulse1, pulse2, wave, noise; + }; + unsigned char channels[LSDJ_CHANNEL_COUNT]; } lsdj_row_t; // Clear all row data to factory settings void lsdj_row_clear(lsdj_row_t* row); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/song.c b/liblsdj/song.c index 3139b2d..f32b837 100644 --- a/liblsdj/song.c +++ b/liblsdj/song.c @@ -101,7 +101,17 @@ struct lsdj_song_t char wordNames[LSDJ_WORD_COUNT][LSDJ_WORD_NAME_LENGTH]; // Bookmarks - unsigned char bookmarks[LSDJ_BOOKMARK_COUNT]; + union + { + struct + { + unsigned char pulse1[LSDJ_BOOKMARK_POSITION_COUNT]; + unsigned char pulse2[LSDJ_BOOKMARK_POSITION_COUNT]; + unsigned char wave[LSDJ_BOOKMARK_POSITION_COUNT]; + unsigned char noise[LSDJ_BOOKMARK_POSITION_COUNT]; + }; + unsigned char channels[LSDJ_BOOKMARK_POSITION_COUNT][LSDJ_CHANNEL_COUNT]; + } bookmarks; struct { @@ -183,7 +193,7 @@ lsdj_song_t* lsdj_song_new(lsdj_error_t** error) lsdj_word_clear(&song->words[i]); memcpy(song->wordNames, DEFAULT_WORD_NAMES, sizeof(song->wordNames)); - memset(song->bookmarks, 0xFF, sizeof(song->bookmarks)); + memset(&song->bookmarks, LSDJ_NO_BOOKMARK, sizeof(song->bookmarks)); song->meta.keyDelay = 7; song->meta.keyRepeat = 2; @@ -247,7 +257,7 @@ lsdj_song_t* lsdj_song_copy(const lsdj_song_t* rhs, lsdj_error_t** error) memcpy(song->grooves, rhs->grooves, sizeof(rhs->grooves)); memcpy(song->words, rhs->words, sizeof(rhs->words)); memcpy(song->wordNames, rhs->wordNames, sizeof(rhs->wordNames)); - memcpy(song->bookmarks, rhs->bookmarks, sizeof(rhs->bookmarks)); + song->bookmarks = rhs->bookmarks; memcpy(&song->meta, &rhs->meta, sizeof(rhs->meta)); @@ -306,7 +316,7 @@ void read_bank0(lsdj_vio_t* vio, lsdj_song_t* song) vio->seek(LSDJ_PHRASE_LENGTH, SEEK_CUR, vio->user_data); } - vio->read(song->bookmarks, sizeof(song->bookmarks), vio->user_data); + vio->read(&song->bookmarks, sizeof(song->bookmarks), vio->user_data); vio->read(song->reserved1030, sizeof(song->reserved1030), vio->user_data); vio->read(song->grooves, sizeof(song->grooves), vio->user_data); vio->read(song->rows, sizeof(song->rows), vio->user_data); @@ -357,7 +367,7 @@ void write_bank0(const lsdj_song_t* song, lsdj_vio_t* vio) vio->write(LSDJ_PHRASE_LENGTH_ZERO, LSDJ_PHRASE_LENGTH, vio->user_data); } - vio->write(song->bookmarks, sizeof(song->bookmarks), vio->user_data); + vio->write(&song->bookmarks, sizeof(song->bookmarks), vio->user_data); vio->write(song->reserved1030, sizeof(song->reserved1030), vio->user_data); vio->write(song->grooves, sizeof(song->grooves), vio->user_data); vio->write(song->rows, sizeof(song->rows), vio->user_data); @@ -420,7 +430,14 @@ void read_soft_synth_parameters(lsdj_vio_t* vio, lsdj_synth_t* synth) vio->read(&synth->cutOffEnd, 1, vio->user_data); vio->read(&synth->phaseEnd, 1, vio->user_data); vio->read(&synth->vshiftEnd, 1, vio->user_data); - vio->read(synth->reserved, 3, vio->user_data); + + unsigned char byte = 0x00; + vio->read(&byte, 1, vio->user_data); + byte = 0xFF - byte; + synth->limitStart = (byte >> 4) & 0xF; + synth->limitEnd = byte & 0xF; + + vio->read(synth->reserved, 2, vio->user_data); } void read_bank1(lsdj_vio_t* vio, lsdj_song_t* song, lsdj_error_t** error) @@ -563,7 +580,11 @@ void write_soft_synth_parameters(const lsdj_synth_t* synth, lsdj_vio_t* vio) vio->write(&synth->cutOffEnd, 1, vio->user_data); vio->write(&synth->phaseEnd, 1, vio->user_data); vio->write(&synth->vshiftEnd, 1, vio->user_data); - vio->write(synth->reserved, 3, vio->user_data); + + unsigned char byte = 0xFF - (unsigned char)((synth->limitStart << 4) | synth->limitEnd); + vio->write(&byte, 1, vio->user_data); + + vio->write(synth->reserved, 2, vio->user_data); } void write_bank1(const lsdj_song_t* song, lsdj_vio_t* vio, lsdj_error_t** error) @@ -1014,17 +1035,70 @@ unsigned char lsdj_song_get_drum_max(const lsdj_song_t* song) return song->drumMax; } +lsdj_row_t* lsdj_song_get_row(lsdj_song_t* song, size_t index) +{ + return &song->rows[index]; +} + +lsdj_chain_t* lsdj_song_get_chain(lsdj_song_t* song, size_t index) +{ + return song->chains[index]; +} + +lsdj_phrase_t* lsdj_song_get_phrase(lsdj_song_t* song, size_t index) +{ + return song->phrases[index]; +} + lsdj_instrument_t* lsdj_song_get_instrument(lsdj_song_t* song, size_t index) { return song->instruments[index]; } +lsdj_synth_t* lsdj_song_get_synth(lsdj_song_t* song, size_t index) +{ + return &song->synths[index]; +} + +lsdj_wave_t* lsdj_song_get_wave(lsdj_song_t* song, size_t index) +{ + return &song->waves[index]; +} + lsdj_table_t* lsdj_song_get_table(lsdj_song_t* song, size_t index) { return song->tables[index]; } -lsdj_phrase_t* lsdj_song_get_phrase(lsdj_song_t* song, size_t index) +lsdj_groove_t* lsdj_song_get_groove(lsdj_song_t* song, size_t index) { - return song->phrases[index]; + return &song->grooves[index]; +} + +lsdj_word_t* lsdj_song_get_word(lsdj_song_t* song, size_t index) +{ + return &song->words[index]; +} + +void lsdj_song_set_word_name(lsdj_song_t* song, size_t index, const char* data, size_t size) +{ + strncpy(song->wordNames[index], data, size < LSDJ_WORD_NAME_LENGTH ? size : LSDJ_WORD_NAME_LENGTH); +} + +void lsdj_song_get_word_name(lsdj_song_t* song, size_t index, char* data, size_t size) +{ + const size_t len = strnlen(song->wordNames[index], LSDJ_WORD_NAME_LENGTH); + strncpy(data, song->wordNames[index], len); + for (size_t i = len; i < LSDJ_WORD_LENGTH; i += 1) + data[i] = '\0'; +} + +void lsdj_song_set_bookmark(lsdj_song_t* song, lsdj_channel_t channel, size_t position, unsigned char bookmark) +{ + song->bookmarks.channels[channel][position] = bookmark; +} + +unsigned char lsdj_song_get_bookmark(lsdj_song_t* song, lsdj_channel_t channel, size_t position) +{ + return song->bookmarks.channels[channel][position]; } diff --git a/liblsdj/song.h b/liblsdj/song.h index 05955f8..e8ddf73 100644 --- a/liblsdj/song.h +++ b/liblsdj/song.h @@ -36,22 +36,33 @@ #ifndef LSDJ_SONG_H #define LSDJ_SONG_H +#ifdef __cplusplus +extern "C" { +#endif + +#include "chain.h" +#include "groove.h" #include "instrument.h" #include "phrase.h" +#include "row.h" +#include "synth.h" #include "table.h" #include "vio.h" +#include "wave.h" +#include "word.h" #define LSDJ_SONG_DECOMPRESSED_SIZE (0x8000) #define LSDJ_ROW_COUNT (256) #define LSDJ_CHAIN_COUNT (128) #define LSDJ_PHRASE_COUNT (0xFF) -#define LSDJ_BOOKMARK_COUNT (64) #define LSDJ_INSTRUMENT_COUNT (64) #define LSDJ_SYNTH_COUNT (16) #define LSDJ_TABLE_COUNT (32) #define LSDJ_WAVE_COUNT (256) #define LSDJ_GROOVE_COUNT (32) #define LSDJ_WORD_COUNT (42) +#define LSDJ_BOOKMARK_POSITION_COUNT (16) +#define LSDJ_NO_BOOKMARK (0xFF) static const unsigned char LSDJ_CLONE_DEEP = 0; static const unsigned char LSDJ_CLONE_SLIM = 1; @@ -83,8 +94,22 @@ unsigned char lsdj_song_get_file_changed_flag(const lsdj_song_t* song); void lsdj_song_set_drum_max(lsdj_song_t* song, unsigned char drumMax); unsigned char lsdj_song_get_drum_max(const lsdj_song_t* song); +lsdj_row_t* lsdj_song_get_row(lsdj_song_t* song, size_t index); +lsdj_chain_t* lsdj_song_get_chain(lsdj_song_t* song, size_t index); +lsdj_phrase_t* lsdj_song_get_phrase(lsdj_song_t* song, size_t index); lsdj_instrument_t* lsdj_song_get_instrument(lsdj_song_t* song, size_t index); +lsdj_synth_t* lsdj_song_get_synth(lsdj_song_t* song, size_t index); +lsdj_wave_t* lsdj_song_get_wave(lsdj_song_t* song, size_t index); lsdj_table_t* lsdj_song_get_table(lsdj_song_t* song, size_t index); -lsdj_phrase_t* lsdj_song_get_phrase(lsdj_song_t* song, size_t index); +lsdj_groove_t* lsdj_song_get_groove(lsdj_song_t* song, size_t index); +lsdj_word_t* lsdj_song_get_word(lsdj_song_t* song, size_t index); +void lsdj_song_set_word_name(lsdj_song_t* song, size_t index, const char* data, size_t size); +void lsdj_song_get_word_name(lsdj_song_t* song, size_t index, char* data, size_t size); +void lsdj_song_set_bookmark(lsdj_song_t* song, lsdj_channel_t channel, size_t position, unsigned char bookmark); +unsigned char lsdj_song_get_bookmark(lsdj_song_t* song, lsdj_channel_t channel, size_t position); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/synth.c b/liblsdj/synth.c index 14c9cda..f123902 100644 --- a/liblsdj/synth.c +++ b/liblsdj/synth.c @@ -53,7 +53,10 @@ void lsdj_synth_clear(lsdj_synth_t* synth) synth->cutOffEnd = 0xFF; synth->phaseEnd = 0; synth->vshiftEnd = 0; - synth->reserved[0] = synth->reserved[1] = synth->reserved[2] = 0; + synth->limitStart = 0xF; + synth->limitEnd = 0xF; + synth->reserved[0] = 0; + synth->reserved[1] = 0; synth->overwritten = 0; } diff --git a/liblsdj/synth.h b/liblsdj/synth.h index dec083e..a3b0f11 100644 --- a/liblsdj/synth.h +++ b/liblsdj/synth.h @@ -36,6 +36,10 @@ #ifndef LSDJ_SYNTH_H #define LSDJ_SYNTH_H +#ifdef __cplusplus +extern "C" { +#endif + static const unsigned char LSDJ_SYNTH_WAVEFORM_SAWTOOTH = 0; static const unsigned char LSDJ_SYNTH_WAVEFORM_SQUARE = 1; static const unsigned char LSDJ_SYNTH_WAVEFORM_TRIANGLE = 2; @@ -47,6 +51,7 @@ static const unsigned char LSDJ_SYNTH_FILTER_ALL_PASS = 3; static const unsigned char LSDJ_SYNTH_DISTORTION_CLIP = 0; static const unsigned char LSDJ_SYNTH_DISTORTION_WRAP = 1; +static const unsigned char LSDJ_SYNTH_DISTORTION_FOLD = 2; static const unsigned char LSDJ_SYNTH_PHASE_NORMAL = 0; static const unsigned char LSDJ_SYNTH_PHASE_RESYNC = 1; @@ -72,7 +77,10 @@ typedef struct unsigned char vshiftStart; unsigned char vshiftEnd; - unsigned char reserved[3]; + unsigned char limitStart; + unsigned char limitEnd; + + unsigned char reserved[2]; unsigned char overwritten; // 0 if false, 1 if true } lsdj_synth_t; @@ -80,4 +88,8 @@ typedef struct // Clear all soft synth data to factory settings void lsdj_synth_clear(lsdj_synth_t* synth); +#ifdef __cplusplus +} +#endif + #endif diff --git a/liblsdj/table.h b/liblsdj/table.h index 07849bc..f28e876 100644 --- a/liblsdj/table.h +++ b/liblsdj/table.h @@ -36,6 +36,10 @@ #ifndef LSDJ_TABLE_H #define LSDJ_TABLE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "command.h" // The default constant length of a table @@ -60,5 +64,9 @@ unsigned char lsdj_table_get_transposition(const lsdj_table_t* table, size_t ind lsdj_command_t* lsdj_table_get_command1(lsdj_table_t* table, size_t index); lsdj_command_t* lsdj_table_get_command2(lsdj_table_t* table, size_t index); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/vio.h b/liblsdj/vio.h index fc64847..9dcc095 100644 --- a/liblsdj/vio.h +++ b/liblsdj/vio.h @@ -38,7 +38,7 @@ #ifdef __cplusplus extern "C" { -#endif /* __cplusplus */ +#endif #include #include @@ -82,6 +82,6 @@ long lsdj_mseek(long offset, int whence, void* user_data); #ifdef __cplusplus } -#endif /* __cplusplus */ +#endif #endif diff --git a/liblsdj/wave.h b/liblsdj/wave.h index 58c66a7..668bc5d 100644 --- a/liblsdj/wave.h +++ b/liblsdj/wave.h @@ -36,6 +36,10 @@ #ifndef LSDJ_WAVE_H #define LSDJ_WAVE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "command.h" // The default length of wave data @@ -50,5 +54,9 @@ typedef struct // Clear all wave data to factory settings void lsdj_wave_clear(lsdj_wave_t* wave); + +#ifdef __cplusplus +} +#endif #endif diff --git a/liblsdj/word.h b/liblsdj/word.h index 6e6b4ad..2d59389 100644 --- a/liblsdj/word.h +++ b/liblsdj/word.h @@ -36,6 +36,10 @@ #ifndef LSDJ_WORD_H #define LSDJ_WORD_H +#ifdef __cplusplus +extern "C" { +#endif + // The default constant length of a word #define LSDJ_WORD_LENGTH (16) @@ -51,5 +55,9 @@ typedef struct // Clear all word data to factory settings void lsdj_word_clear(lsdj_word_t* word); + +#ifdef __cplusplus +} +#endif #endif diff --git a/lsdj_mono/CMakeLists.txt b/lsdj_mono/CMakeLists.txt index 8433530..b335d55 100644 --- a/lsdj_mono/CMakeLists.txt +++ b/lsdj_mono/CMakeLists.txt @@ -4,8 +4,8 @@ set(Boost_USE_STATIC_LIBS ON) find_package(Boost REQUIRED COMPONENTS filesystem program_options) # Create the executable target -add_executable(lsdj-mono main.cpp) -source_group(\\ FILES main.cpp) +add_executable(lsdj-mono main.cpp ../common/common.hpp ../common/common.cpp) +source_group(\\ FILES main.cpp ../common/common.hpp ../common/common.cpp) target_compile_features(lsdj-mono PUBLIC cxx_std_14) target_include_directories(lsdj-mono PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/lsdj_mono/main.cpp b/lsdj_mono/main.cpp index 3789a65..fda4f94 100644 --- a/lsdj_mono/main.cpp +++ b/lsdj_mono/main.cpp @@ -38,6 +38,7 @@ #include #include +#include "../common/common.hpp" #include "../liblsdj/sav.h" bool verbose = false; @@ -56,16 +57,6 @@ bool alreadyEndsWithMono(const boost::filesystem::path& path) return stem.size() >= 5 && stem.substr(stem.size() - 5) == ".MONO"; } -bool isHiddenFile(const std::string& str) -{ - switch (str.size()) - { - case 0: return true; - case 1: return false; - default: return str[0] == '.' && str[1] != '.' && str[1] != '/'; - } -} - void convertInstrument(lsdj_instrument_t* instrument) { if (instrument != nullptr && lsdj_instrument_get_panning(instrument) != LSDJ_PAN_NONE) @@ -214,7 +205,7 @@ int processDirectory(const boost::filesystem::path& path) int process(const boost::filesystem::path& path) { - if (isHiddenFile(path.filename().string())) + if (lsdj::isHiddenFile(path.filename().string())) return 0; if (boost::filesystem::is_directory(path)) diff --git a/lsdsng_export/CMakeLists.txt b/lsdsng_export/CMakeLists.txt index 0a10191..2e21e91 100644 --- a/lsdsng_export/CMakeLists.txt +++ b/lsdsng_export/CMakeLists.txt @@ -4,8 +4,8 @@ set(Boost_USE_STATIC_LIBS ON) find_package(Boost REQUIRED COMPONENTS filesystem program_options) # Create the executable target -add_executable(lsdsng-export main.cpp) -source_group(\\ FILES main.cpp) +add_executable(lsdsng-export main.cpp exporter.hpp exporter.cpp ../common/common.hpp ../common/common.cpp) +source_group(\\ FILES main.cpp exporter.hpp exporter.cpp ../common/common.hpp ../common/common.cpp) target_compile_features(lsdsng-export PUBLIC cxx_std_14) target_include_directories(lsdsng-export PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/lsdsng_export/exporter.cpp b/lsdsng_export/exporter.cpp new file mode 100644 index 0000000..f19b33f --- /dev/null +++ b/lsdsng_export/exporter.cpp @@ -0,0 +1,303 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#include +#include +#include + +#include + +#include "../common/common.hpp" +#include "exporter.hpp" + +namespace lsdj +{ + int Exporter::exportProjects(const boost::filesystem::path& path, const std::string& output) + { + // Load in the save file + lsdj_error_t* error = nullptr; + lsdj_sav_t* sav = lsdj_sav_read_from_file(path.string().c_str(), &error); + if (sav == nullptr) + return handle_error(error); + + if (verbose) + std::cout << "Read '" << path.string() << "'" << std::endl; + + const auto outputFolder = boost::filesystem::absolute(output); + + // If no specific indices were given, or -w was flagged (index == -1), + // display the working memory song as well + if ((indices.empty() && names.empty()) || std::find(std::begin(indices), std::end(indices), -1) != std::end(indices)) + { + lsdj_project_t* project = lsdj_project_new_from_working_memory_song(sav, &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + + exportProject(project, outputFolder, true, &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + } + + // Go through every project + const auto count = lsdj_sav_get_project_count(sav); + for (int i = 0; i < count; ++i) + { + // See if we're using indices and this project hasn't been specified + // If so, skip it and move on to the next one + if (!indices.empty() && std::find(std::begin(indices), std::end(indices), i) == std::end(indices)) + continue; + + // Retrieve the project + lsdj_project_t* project = lsdj_sav_get_project(sav, i); + + // See if we're using name-based specification and whether this project has been singled out + // If not, skip it and move on to the next one + if (!names.empty()) + { + char name[9]; + std::fill_n(name, 9, '\0'); + lsdj_project_get_name(project, name, sizeof(name)); + const auto namestr = std::string(name); + if (std::find_if(std::begin(names), std::end(names), [&](const auto& x){ return compareCaseInsensitive(x, namestr); }) == std::end(names)) + continue; + } + + // Does this project contain a song? If not, it's empty + if (lsdj_project_get_song(project) == NULL) + continue; + + // Export the project + exportProject(project, outputFolder, false, &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + } + + lsdj_sav_free(sav); + + return 0; + } + + void Exporter::exportProject(const lsdj_project_t* project, boost::filesystem::path folder, bool workingMemory, lsdj_error_t** error) + { + auto name = constructName(project); + if (name.empty()) + name = "(EMPTY)"; + + boost::filesystem::path path = folder; + + if (putInFolder) + path /= name; + boost::filesystem::create_directories(folder); + + std::stringstream stream; + stream << name << convertVersionToString(lsdj_project_get_version(project), true); + + if (workingMemory) + stream << ".WM"; + + stream << ".lsdsng"; + path /= stream.str(); + + lsdj_project_write_lsdsng_to_file(project, path.string().c_str(), error); + if (*error != nullptr) + return; + + // Let the user know if verbose output has been toggled on + if (verbose) + { + std::cout << "Exported " << boost::filesystem::relative(path, folder).string() << std::endl; + } + } + + int Exporter::print(const boost::filesystem::path& path) + { + // Try and read the sav + lsdj_error_t* error = nullptr; + lsdj_sav_t* sav = lsdj_sav_read_from_file(path.string().c_str(), &error); + if (sav == nullptr) + return lsdj::handle_error(error); + + // Header + std::cout << "# Name "; + if (versionStyle != VersionStyle::NONE) + std::cout << "Ver "; + std::cout << "Fmt" << std::endl; + + // If no specific indices were given, or -w was flagged (index == -1), + // display the working memory song as well + if ((indices.empty() && names.empty()) || std::find(std::begin(indices), std::end(indices), -1) != std::end(indices)) + { + printWorkingMemorySong(sav); + } + + // Go through all compressed projects + const auto count = lsdj_sav_get_project_count(sav); + for (int i = 0; i < count; ++i) + { + // If indices were specified and this project wasn't one of them, move on to the next + if (!indices.empty() && std::find(std::begin(indices), std::end(indices), i) == std::end(indices)) + continue; + + printProject(sav, i); + } + + return 0; + } + + std::string Exporter::convertVersionToString(unsigned char version, bool prefixDot) const + { + std::ostringstream stream; + + switch (versionStyle) + { + case VersionStyle::NONE: + break; + case VersionStyle::HEX: + if (prefixDot) + stream << '.'; + stream << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(version); + break; + case VersionStyle::DECIMAL: + if (prefixDot) + stream << '.'; + stream << std::setfill('0') << std::setw(3) << static_cast(version); + break; + } + + return stream.str(); + } + + void Exporter::printWorkingMemorySong(const lsdj_sav_t* sav) + { + std::cout << "WM "; + + // If the working memory song represent one of the projects, display that name + const auto active = lsdj_sav_get_active_project(sav); + if (active != LSDJ_NO_ACTIVE_PROJECT) + { + lsdj_project_t* project = lsdj_sav_get_project(sav, active); + + const auto name = constructName(project); + std::cout << name; + for (auto i = 0; i < (9 - name.length()); ++i) + std::cout << ' '; + } else { + // The working memory doesn't represent one of the projects, so it + // doesn't really have a name + std::cout << " "; + } + + // Display whether the working memory song is "dirty"/edited, and display that + // as version number (it doesn't really have a version number otherwise) + const lsdj_song_t* song = lsdj_sav_get_working_memory_song(sav); + if (lsdj_song_get_file_changed_flag(song)) + { + switch (versionStyle) + { + case VersionStyle::NONE: break; + case VersionStyle::HEX: + std::cout << (lsdj_song_get_file_changed_flag(song) ? "*" : " ") << " \t"; + break; + case VersionStyle::DECIMAL: + std::cout << (lsdj_song_get_file_changed_flag(song) ? "*" : " ") << " \t"; + break; + } + } + + // Display the format version of the song + std::cout << std::to_string(lsdj_song_get_format_version(song)) << std::endl; + } + + void Exporter::printProject(const lsdj_sav_t* sav, std::size_t index) + { + // Retrieve the project + const lsdj_project_t* project = lsdj_sav_get_project(sav, index); + + // See if we're using name-based specification and whether this project has been singled out + // If not, skip it and move on to the next one + if (!names.empty()) + { + char name[9]; + std::fill_n(name, 9, '\0'); + lsdj_project_get_name(project, name, sizeof(name)); + const auto namestr = std::string(name); + if (std::find_if(std::begin(names), std::end(names), [&](const auto& x){ return lsdj::compareCaseInsensitive(x, namestr); }) == std::end(names)) + return; + } + + // Retrieve the song belonging to this project, make sure it's there + const lsdj_song_t* song = lsdj_project_get_song(project); + if (!song) + return; + + // Print out the index + std::cout << std::to_string(index) << " "; + if (index < 10) + std::cout << ' '; + + // Display the name of the project + const auto name = constructName(project); + std::cout << name; + + for (auto i = 0; i < (9 - name.length()); ++i) + std::cout << ' '; + + // Dipslay the version number of the project + std::cout << convertVersionToString(lsdj_project_get_version(project), false); + switch (versionStyle) + { + case VersionStyle::NONE: break; + case VersionStyle::HEX: std::cout << " \t"; break; + case VersionStyle::DECIMAL: std::cout << "\t"; break; + } + + // Retrieve the sav format version of the song and display it as well + std::cout << std::to_string(lsdj_song_get_format_version(song)) << std::endl; + } + + std::string Exporter::constructName(const lsdj_project_t* project) + { + return constructProjectName(project, underscore); + } +} diff --git a/lsdsng_export/exporter.hpp b/lsdsng_export/exporter.hpp new file mode 100644 index 0000000..0dc7c66 --- /dev/null +++ b/lsdsng_export/exporter.hpp @@ -0,0 +1,87 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#ifndef LSDJ_EXPORTER_HPP +#define LSDJ_EXPORTER_HPP + +#include + +#include "../liblsdj/error.h" +#include "../liblsdj/project.h" +#include "../liblsdj/sav.h" + +namespace lsdj +{ + class Exporter + { + public: + enum class VersionStyle + { + NONE, + HEX, + DECIMAL + }; + + public: + int exportProjects(const boost::filesystem::path& path, const std::string& output); + void exportProject(const lsdj_project_t* project, boost::filesystem::path folder, bool workingMemory, lsdj_error_t** error); + int print(const boost::filesystem::path& path); + + public: + // The version exporting style + VersionStyle versionStyle = VersionStyle::HEX; + + bool underscore = false; + bool putInFolder = false; + bool verbose = false; + + std::vector indices; + std::vector names; + + private: + // Converts a project version to a string representation using the current VersionStyle + std::string convertVersionToString(unsigned char version, bool prefixDot) const; + + // Print the working memory song line + void printWorkingMemorySong(const lsdj_sav_t* sav); + + // Print a sav project line + void printProject(const lsdj_sav_t* sav, std::size_t index); + + std::string constructName(const lsdj_project_t* project); + }; +} + +#endif diff --git a/lsdsng_export/main.cpp b/lsdsng_export/main.cpp index bbfc902..2ec1659 100644 --- a/lsdsng_export/main.cpp +++ b/lsdsng_export/main.cpp @@ -42,286 +42,7 @@ #include #include "../liblsdj/sav.h" - -bool compareCaseInsensitive(std::string str1, std::string str2) -{ - std::transform(str1.begin(), str1.end(), str1.begin(), ::tolower); - std::transform(str2.begin(), str2.end(), str2.begin(), ::tolower); - return str1 == str2; -} - -enum class VersionStyle -{ - NONE, - HEX, - DECIMAL -}; - -VersionStyle versionStyle; -bool underscore = false; -bool putInFolder = false; -bool verbose = false; -std::vector indices; -std::vector names; - -int handle_error(lsdj_error_t* error) -{ - std::cerr << "ERROR: " << lsdj_error_get_c_str(error) << std::endl; - lsdj_error_free(error); - return 1; -} - -std::string constructName(const lsdj_project_t* project, bool underscore) -{ - char name[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - lsdj_project_get_name(project, name, sizeof(name)); - - if (underscore) - std::replace(name, name + 9, 'x', '_'); - - return name; -} - -void exportProject(const lsdj_project_t* project, boost::filesystem::path folder, VersionStyle versionStyle, bool underscore, bool putInFolder, bool workingMemory, lsdj_error_t** error) -{ - auto name = constructName(project, underscore); - if (name.empty()) - name = "(EMPTY)"; - - if (putInFolder) - folder /= name; - boost::filesystem::create_directories(folder); - - std::stringstream stream; - stream << name; - switch (versionStyle) - { - case VersionStyle::NONE: break; - case VersionStyle::HEX: - stream << "." << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)lsdj_project_get_version(project); - break; - case VersionStyle::DECIMAL: - stream << "." << std::setfill('0') << std::setw(3) << (unsigned int)lsdj_project_get_version(project); - break; - } - - if (workingMemory) - stream << ".WM"; - - stream << ".lsdsng"; - folder /= stream.str(); - - lsdj_project_write_lsdsng_to_file(project, folder.string().c_str(), error); -} - -// Export all songs of a file -int exportSongs(const boost::filesystem::path& path, const std::string& output) -{ - // Load in the save file - lsdj_error_t* error = nullptr; - lsdj_sav_t* sav = lsdj_sav_read_from_file(path.string().c_str(), &error); - if (sav == nullptr) - return handle_error(error); - - if (verbose) - std::cout << "Read '" << path.string() << "'" << std::endl; - - const auto outputFolder = boost::filesystem::absolute(output); - - // If no specific indices were given, or -w was flagged (index == -1), - // display the working memory song as well - if ((indices.empty() && names.empty()) || std::find(std::begin(indices), std::end(indices), -1) != std::end(indices)) - { - lsdj_project_t* project = lsdj_project_new_from_working_memory_song(sav, &error); - if (error) - { - lsdj_sav_free(sav); - return handle_error(error); - } - - exportProject(project, outputFolder, versionStyle, underscore, putInFolder, true, &error); - if (error) - { - lsdj_sav_free(sav); - return handle_error(error); - } - } - - // Go through every project - const auto count = lsdj_sav_get_project_count(sav); - for (int i = 0; i < count; ++i) - { - // See if we're using indices and this project hasn't been specified - // If so, skip it and move on to the next one - if (!indices.empty() && std::find(std::begin(indices), std::end(indices), i) == std::end(indices)) - continue; - - // Retrieve the project - lsdj_project_t* project = lsdj_sav_get_project(sav, i); - - // See if we're using name-based specification and whether this project has been singled out - // If not, skip it and move on to the next one - if (!names.empty()) - { - char name[9]; - std::fill_n(name, 9, '\0'); - lsdj_project_get_name(project, name, sizeof(name)); - const auto namestr = std::string(name); - if (std::find_if(std::begin(names), std::end(names), [&](const auto& x){ return compareCaseInsensitive(x, namestr); }) == std::end(names)) - continue; - } - - // Does this project contain a song? If not, it's empty - if (lsdj_project_get_song(project) == NULL) - continue; - - // Export the project - exportProject(project, outputFolder, versionStyle, underscore, putInFolder, false, &error); - if (error) - { - lsdj_sav_free(sav); - return handle_error(error); - } - - // Let the user know if verbose output has been toggled on - if (verbose) - { - std::cout << "Exported " << constructName(project, underscore); - - switch (versionStyle) - { - case VersionStyle::NONE: break; - case VersionStyle::HEX: - std::cout << " (" << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)lsdj_project_get_version(project) << ")"; - break; - case VersionStyle::DECIMAL: - std::cout << " (" << std::setfill('0') << std::setw(3) << (unsigned int)lsdj_project_get_version(project) << ")"; - break; - } - - std::cout << std::endl; - } - } - - lsdj_sav_free(sav); - - return 0; -} - -// Print the contents of a file -int print(const boost::filesystem::path& path) -{ - // Try and read the sav - lsdj_error_t* error = nullptr; - lsdj_sav_t* sav = lsdj_sav_read_from_file(path.string().c_str(), &error); - if (sav == nullptr) - return handle_error(error); - - // Header - std::cout << "# Name "; - if (versionStyle != VersionStyle::NONE) - std::cout << "Ver "; - std::cout << "Fmt" << std::endl; - - // If no specific indices were given, or -w was flagged (index == -1), - // display the working memory song as well - if ((indices.empty() && names.empty()) || std::find(std::begin(indices), std::end(indices), -1) != std::end(indices)) - { - std::cout << "WM. "; - - // If the working memory song represent one of the projects, display that name - const auto active = lsdj_sav_get_active_project(sav); - if (active != LSDJ_NO_ACTIVE_PROJECT) - { - lsdj_project_t* project = lsdj_sav_get_project(sav, active); - - const auto name = constructName(project, underscore); - std::cout << name; - for (auto i = 0; i < (9 - name.length()); ++i) - std::cout << ' '; - } else { - // The working memory doesn't represent one of the projects, so it - // doesn't really have a name - std::cout << " "; - } - - // Display whether the working memory song is "dirty"/edited, and display that - // as version number (it doesn't really have a version number otherwise) - const lsdj_song_t* song = lsdj_sav_get_working_memory_song(sav); - if (versionStyle != VersionStyle::NONE) - { - if (lsdj_song_get_file_changed_flag(song)) - std::cout << "* "; - else - std::cout << " "; - } - - // Display the format version of the song - std::cout << std::to_string(lsdj_song_get_format_version(song)); - - std::cout << std::endl; - } - - // Go through all compressed projects - const auto count = lsdj_sav_get_project_count(sav); - for (int i = 0; i < count; ++i) - { - // If indices were specified and this project wasn't one of them, move on to the next - if (!indices.empty() && std::find(std::begin(indices), std::end(indices), i) == std::end(indices)) - continue; - - // Retrieve the project - const lsdj_project_t* project = lsdj_sav_get_project(sav, i); - - // See if we're using name-based specification and whether this project has been singled out - // If not, skip it and move on to the next one - if (!names.empty()) - { - char name[9]; - std::fill_n(name, 9, '\0'); - lsdj_project_get_name(project, name, sizeof(name)); - const auto namestr = std::string(name); - if (std::find_if(std::begin(names), std::end(names), [&](const auto& x){ return compareCaseInsensitive(x, namestr); }) == std::end(names)) - continue; - } - - // Retrieve the song belonging to this project, make sure it's there - const lsdj_song_t* song = lsdj_project_get_song(project); - if (!song) - continue; - - // Print out the index - std::cout << std::to_string(i) << ". "; - if (i < 10) - std::cout << ' '; - - // Display the name of the project - const auto name = constructName(project, underscore); - std::cout << name; - - for (auto i = 0; i < (9 - name.length()); ++i) - std::cout << ' '; - - // Dipslay the version number of the project - switch (versionStyle) - { - case VersionStyle::NONE: break; - case VersionStyle::HEX: - std::cout << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)lsdj_project_get_version(project) << " "; - break; - case VersionStyle::DECIMAL: - std::cout << std::setfill('0') << std::setw(3) << (unsigned int)lsdj_project_get_version(project) << " "; - break; - } - - // Retrieve the sav format version of the song and display it as well - std::cout << std::to_string(lsdj_song_get_format_version(song)); - - std::cout << std::endl; - } - - return 0; -} +#include "exporter.hpp" int main(int argc, char* argv[]) { @@ -370,25 +91,28 @@ int main(int argc, char* argv[]) return 1; } + // Create the exporter, that will do the work + lsdj::Exporter exporter; + // Parse some of the flags manipulating output "style" - versionStyle = vm.count("noversion") ? VersionStyle::NONE : vm.count("decimal") ? VersionStyle::DECIMAL : VersionStyle::HEX; - underscore = vm.count("underscore"); - putInFolder = vm.count("folder"); - verbose = vm.count("verbose"); + exporter.versionStyle = vm.count("noversion") ? lsdj::Exporter::VersionStyle::NONE : vm.count("decimal") ? lsdj::Exporter::VersionStyle::DECIMAL : lsdj::Exporter::VersionStyle::HEX; + exporter.underscore = vm.count("underscore"); + exporter.putInFolder = vm.count("folder"); + exporter.verbose = vm.count("verbose"); // Has the user specified one or more specific indices to export? if (vm.count("index")) - indices = vm["index"].as>(); + exporter.indices = vm["index"].as>(); if (vm.count("working-memory")) - indices.emplace_back(-1); // -1 represents working memory, kind-of a hack, but meh :/ + exporter.indices.emplace_back(-1); // -1 represents working memory, kind-of a hack, but meh :/ if (vm.count("name")) - names = vm["name"].as>(); + exporter.names = vm["name"].as>(); // Has the user requested a print, or an actual export? if (vm.count("print")) - return print(path); + return exporter.print(path); else - return exportSongs(path, vm["output"].as()); + return exporter.exportProjects(path, vm["output"].as()); } else { std::cout << desc << std::endl; return 0; diff --git a/lsdsng_import/CMakeLists.txt b/lsdsng_import/CMakeLists.txt index f658684..3ebf7aa 100644 --- a/lsdsng_import/CMakeLists.txt +++ b/lsdsng_import/CMakeLists.txt @@ -4,8 +4,8 @@ set(Boost_USE_STATIC_LIBS ON) find_package(Boost REQUIRED COMPONENTS filesystem program_options) # Create the executable target -add_executable(lsdsng-import main.cpp) -source_group(\\ FILES main.cpp) +add_executable(lsdsng-import main.cpp importer.hpp importer.cpp ../common/common.hpp ../common/common.cpp) +source_group(\\ FILES main.cpp importer.hpp importer.cpp ../common/common.hpp ../common/common.cpp) target_compile_features(lsdsng-import PUBLIC cxx_std_14) target_include_directories(lsdsng-import PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/lsdsng_import/importer.cpp b/lsdsng_import/importer.cpp new file mode 100644 index 0000000..dab69f1 --- /dev/null +++ b/lsdsng_import/importer.cpp @@ -0,0 +1,232 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +#include +#include +#include + +#include "../liblsdj/song.h" +#include "../liblsdj/project.h" + +#include "../common/common.hpp" +#include "importer.hpp" + +namespace lsdj +{ + // Scan a path, see whether it's either an .lsdsng or a folder containing .lsdsng's + // Returns the path to the .WM (working memory) file, or {} is there was none + boost::filesystem::path scanPath(const boost::filesystem::path& path, std::vector& paths) + { + if (isHiddenFile(path.filename().string())) + return {}; + + if (boost::filesystem::is_regular_file(path)) + { + paths.emplace_back(path); + return {}; + } + else if (boost::filesystem::is_directory(path)) + { + boost::filesystem::path workingMemoryPath; + std::vector contents; + for (auto it = boost::filesystem::directory_iterator(path); it != boost::filesystem::directory_iterator(); ++it) + { + const auto path = it->path(); + if (isHiddenFile(path.filename().string()) || !boost::filesystem::is_regular_file(path) || path.extension() != ".lsdsng") + continue; + + const auto str = path.stem().string(); + if (str.size() >= 3 && str.substr(str.length() - 3) == ".WM") + { + workingMemoryPath = path; + continue; + } + + contents.emplace_back(path); + } + + std::sort(contents.begin(), contents.end()); + for (auto& path : contents) + paths.emplace_back(path); + + return workingMemoryPath; + } else { + throw std::runtime_error(path.string() + " is not a file or directory"); + } + } + + int Importer::importSongs(const char* savName) + { + // Try to load the provided destination sav, or create a new one + lsdj_error_t* error = nullptr; + lsdj_sav_t* sav = savName ? lsdj_sav_read_from_file(boost::filesystem::absolute(savName).string().c_str(), &error) : lsdj_sav_new(&error); + if (error) + return handle_error(error); + + // Find the first available project slot index + auto index = 0; + for( ; index < lsdj_sav_get_project_count(sav); ++index) + { + lsdj_project_t* project = lsdj_sav_get_project(sav, index); + lsdj_song_t* song = lsdj_project_get_song(project); + if (!song) + break; + } + + if (savName && verbose) + std::cout << "Read " << savName << ", containing " << std::to_string(index) << " saves" << std::endl; + + // Go through all input files and recursively find all .lsdsngs's (and the working memory file) + std::vector paths; + for (auto& input : inputs) + { + const auto wm = scanPath(boost::filesystem::absolute(input), paths); + if (!wm.empty()) + { + if (!workingMemoryPath.empty()) + { + std::cerr << "Multiple working memory (.WM) .lsdsng's found" << std::endl; + return 1; + } + + workingMemoryPath = wm; + } + } + + // Construct an output file name if it's empty + if (outputFile.empty()) + { + if (inputs.size() == 1) + outputFile = std::string(paths.front().filename().string()) + ".sav"; + else + outputFile = "out.sav"; + } + + // Import all lsdsng files + const auto active = lsdj_sav_get_active_project(sav); + for (auto i = 0; i < paths.size(); ++i) + { + if (index == lsdj_sav_get_project_count(sav)) + { + std::cerr << "Reached maximum project count, can't write " << paths[i].string() << std::endl; + break; + } + + importSong(paths[i].string(), sav, index, active, &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + + index += 1; + } + + if (!workingMemoryPath.empty()) + { + importWorkingMemorySong(sav, paths, &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + } + + if (outputFile.empty()) + outputFile = "out.sav"; + + // Write the sav to file + lsdj_sav_write_to_file(sav, boost::filesystem::absolute(outputFile).string().c_str(), &error); + if (error) + { + lsdj_sav_free(sav); + return handle_error(error); + } + + return 0; + } + + void Importer::importSong(const std::string& path, lsdj_sav_t* sav, unsigned char index, unsigned char active, lsdj_error_t** error) + { + lsdj_project_t* project = lsdj_project_read_lsdsng_from_file(path.c_str(), error); + if (*error != nullptr) + return; + + lsdj_sav_set_project(sav, index, project, error); + if (*error != nullptr) + return lsdj_project_free(project); + + std::array name; + name.fill('\0'); + lsdj_project_get_name(project, name.data(), 8); + + if (verbose) + std::cout << "Imported " << name.data() << " at slot " << std::to_string(index) << std::endl; + + if (index == 0 && active == LSDJ_NO_ACTIVE_PROJECT && workingMemoryPath.empty()) + { + lsdj_sav_set_working_memory_song_from_project(sav, index, error); + if (*error != nullptr) + return lsdj_project_free(project); + } + } + + void Importer::importWorkingMemorySong(lsdj_sav_t* sav, const std::vector& paths, lsdj_error_t** error) + { + lsdj_project_t* project = lsdj_project_read_lsdsng_from_file(workingMemoryPath.string().c_str(), error); + if (*error != nullptr) + return lsdj_project_free(project); + + lsdj_song_t* song = lsdj_song_copy(lsdj_project_get_song(project), error); + if (*error != nullptr) + return lsdj_project_free(project); + + int active = LSDJ_NO_ACTIVE_PROJECT; + const auto str = workingMemoryPath.stem().string(); + const auto stem = str.substr(0, str.size() - 3); + for (int i = 0; i != paths.size(); ++i) + { + if (stem == paths[i].stem().string()) + { + active = i; + break; + } + } + + lsdj_sav_set_working_memory_song(sav, song, active); + + lsdj_project_free(project); + } +} diff --git a/lsdsng_import/importer.hpp b/lsdsng_import/importer.hpp new file mode 100644 index 0000000..7f9aaf5 --- /dev/null +++ b/lsdsng_import/importer.hpp @@ -0,0 +1,67 @@ +/* + + This file is a part of liblsdj, a C library for managing everything + that has to do with LSDJ, software for writing music (chiptune) with + your gameboy. For more information, see: + + * https://github.com/stijnfrishert/liblsdj + * http://www.littlesounddj.com + + -------------------------------------------------------------------------------- + + MIT License + + Copyright (c) 2018 Stijn Frishert + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +#ifndef LSDJ_IMPORTER_HPP +#define LSDJ_IMPORTER_HPP + +#include +#include +#include + +#include "../liblsdj/error.h" +#include "../liblsdj/sav.h" + +namespace lsdj +{ + class Importer + { + public: + int importSongs(const char* savName); + + public: + std::vector inputs; + std::string outputFile; + bool verbose = false; + + private: + void importSong(const std::string& path, lsdj_sav_t* sav, unsigned char index, unsigned char active, lsdj_error_t** error); + void importWorkingMemorySong(lsdj_sav_t* sav, const std::vector& paths, lsdj_error_t** error); + + private: + //! The path that refers to the working memory song + boost::filesystem::path workingMemoryPath; + }; +} + +#endif diff --git a/lsdsng_import/main.cpp b/lsdsng_import/main.cpp index d8219da..6dfa713 100644 --- a/lsdsng_import/main.cpp +++ b/lsdsng_import/main.cpp @@ -33,184 +33,11 @@ */ -#include #include -#include #include -#include -#include "../liblsdj/sav.h" - -bool verbose = false; - -int handle_error(lsdj_error_t* error) -{ - std::cerr << "ERROR: " << lsdj_error_get_c_str(error) << std::endl; - lsdj_error_free(error); - return 1; -} - -bool isHiddenFile(const std::string& str) -{ - switch (str.size()) - { - case 0: return true; - case 1: return false; - default: return str[0] == '.' && str[1] != '.' && str[1] != '/'; - } -} - -int importSongs(const std::vector& inputs, std::string outputFile, const char* savName) -{ - lsdj_error_t* error = nullptr; - lsdj_sav_t* sav = savName ? lsdj_sav_read_from_file(boost::filesystem::absolute(savName).string().c_str(), &error) : lsdj_sav_new(&error); - if (error) - return handle_error(error); - - auto index = 0; - for( ; index < lsdj_sav_get_project_count(sav); ++index) - { - lsdj_project_t* project = lsdj_sav_get_project(sav, index); - lsdj_song_t* song = lsdj_project_get_song(project); - if (!song) - break; - } - - if (savName) - std::cout << "Read " << savName << ", containing " << std::to_string(index) << " saves" << std::endl; - - std::vector paths; - boost::filesystem::path workingMemoryPath; - for (auto& input : inputs) - { - const auto path = boost::filesystem::absolute(input); - if (isHiddenFile(path.filename().string())) - continue; - - if (boost::filesystem::is_regular_file(path)) - { - paths.emplace_back(path); - } - else if (boost::filesystem::is_directory(path)) - { - std::vector contents; - for (auto it = boost::filesystem::directory_iterator(path); it != boost::filesystem::directory_iterator(); ++it) - { - const auto path = it->path(); - if (isHiddenFile(path.filename().string()) || !boost::filesystem::is_regular_file(path) || path.extension() != ".lsdsng") - continue; - - const auto str = path.stem().string(); - if (str.size() >= 3 && str.substr(str.length() - 3) == ".WM") - { - workingMemoryPath = path; - continue; - } - - contents.emplace_back(path); - } - - std::sort(contents.begin(), contents.end()); - for (auto& path : contents) - paths.emplace_back(path); - } else { - throw std::runtime_error(path.string() + " is not a file or directory"); - } - } - - const auto active = lsdj_sav_get_active_project(sav); - for (auto i = 0; i < paths.size(); ++i) - { - if (index == lsdj_sav_get_project_count(sav)) - break; - - lsdj_project_t* project = lsdj_project_read_lsdsng_from_file(paths[i].string().c_str(), &error); - if (error) - { - lsdj_sav_free(sav); - return handle_error(error); - } - - lsdj_sav_set_project(sav, index, project, &error); - if (error) - { - lsdj_project_free(project); - lsdj_sav_free(sav); - return handle_error(error); - } - - std::array name; - name.fill('\0'); - lsdj_project_get_name(project, name.data(), 8); - - if (outputFile.empty() && inputs.size() == 1) - outputFile = std::string(name.data()) + ".sav"; - - if (verbose) - std::cout << "Imported " << name.data() << " at slot " << std::to_string(index) << std::endl; - - if (i == 0 && active == LSDJ_NO_ACTIVE_PROJECT && workingMemoryPath.empty()) - { - lsdj_sav_set_working_memory_song_from_project(sav, i, &error); - if (error) - { - lsdj_project_free(project); - lsdj_sav_free(sav); - return handle_error(error); - } - } - - index += 1; - } - - if (!workingMemoryPath.empty()) - { - lsdj_project_t* project = lsdj_project_read_lsdsng_from_file(workingMemoryPath.string().c_str(), &error); - if (error) - { - lsdj_project_free(project); - lsdj_sav_free(sav); - return handle_error(error); - } - - lsdj_song_t* song = lsdj_song_copy(lsdj_project_get_song(project), &error); - if (error) - { - lsdj_project_free(project); - lsdj_sav_free(sav); - return handle_error(error); - } - - int active = LSDJ_NO_ACTIVE_PROJECT; - const auto str = workingMemoryPath.stem().string(); - const auto stem = str.substr(0, str.size() - 3); - for (int i = 0; i != paths.size(); ++i) - { - if (stem == paths[i].stem().string()) - { - active = i; - break; - } - } - - lsdj_sav_set_working_memory_song(sav, song, active); - - lsdj_project_free(project); - } - - if (outputFile.empty()) - outputFile = "out.sav"; - - lsdj_sav_write_to_file(sav, boost::filesystem::absolute(outputFile).string().c_str(), &error); - if (error) - { - lsdj_sav_free(sav); - return handle_error(error); - } - - return 0; -} +#include "importer.hpp" int main(int argc, char* argv[]) { @@ -239,11 +66,13 @@ int main(int argc, char* argv[]) std::cout << desc << std::endl; return 0; } else if (vm.count("file")) { - verbose = vm.count("verbose"); + lsdj::Importer importer; + + importer.inputs = vm["file"].as>(); + importer.outputFile = vm.count("output") ? vm["output"].as() : ""; + importer.verbose = vm.count("verbose"); - return importSongs(vm["file"].as>(), - vm.count("output") ? vm["output"].as() : "", - vm.count("sav") ? vm["sav"].as().c_str() : nullptr); + return importer.importSongs(vm.count("sav") ? vm["sav"].as().c_str() : nullptr); } else { std::cout << desc << std::endl; return 0;