diff --git a/Makefile b/Makefile index 514295a..883dabb 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++17 -fno-exceptions ASFLAGS := -g $(ARCH) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lcitro2d -lcitro3d -lmpg123 -lopusfile -lopus -lvorbisidec -logg -lFLAC -lWildMidi -ljansson -lctrud -lm +LIBS := -lcitro2d -lcitro3d -lmpg123 -lopusfile -lopus -lvorbisidec -logg -lFLAC -lWildMidi -lxmp -ljansson -lctrud -lm # `curl-config --libs` #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/README.md b/README.md index 6618eb0..b1cb976 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ https://github.com/Oreo639/LimePlayer3DS/wiki/config.json LimePlayer3DS is currently in alpha so not all features are going to be avalible. ## Officaly Supported Formats -Mp3, Wav, Flac, Midi, Xmi, Mus, Hmi, Hmp, Ogg vorbis, and Opus. +Mp3, Wav, Flac, Midi, Xmi, Mus, Hmi, Hmp, Mod, Xm, S3m, It, Ogg vorbis, and Opus. ## Building ### Prerequsites: [dkp-pacman](https://devkitpro.org/wiki/devkitPro_pacman) -3ds-dev 3ds-mpg123 3ds-libvorbisidec 3ds-opusfile 3ds-flac 3ds-wildmidi 3ds-jansson 3ds-pkg-config +3ds-dev 3ds-mpg123 3ds-libvorbisidec 3ds-opusfile 3ds-flac 3ds-wildmidi 3ds-xmp 3ds-jansson 3ds-pkg-config You may also need [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/bannertool/) for cia building (optional). diff --git a/src/file.cpp b/src/file.cpp index 79405fc..0c49a67 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "file.hpp" #include "formats/vorbis.hpp" @@ -85,6 +86,10 @@ int File::GetFileType(const std::string& filename) { else if (!strncmp(magic, "FORM", 4) && !strncmp(magic+8, "XDIRINFO", 8)) return FILE_MIDI; + /* MOD, XM, S3M, IT, etc. */ + else if (xmp_test_module(filename.c_str(), NULL) == 0) + return FILE_MOD; + return -1; //Negetive means that the file is not officaly supported. } diff --git a/src/file.hpp b/src/file.hpp index a0a089d..81c9e7f 100644 --- a/src/file.hpp +++ b/src/file.hpp @@ -12,6 +12,7 @@ typedef enum FILE_OPUS, ///< Ogg Opus FILE_MP3, ///< Mp3 FILE_MIDI, ///< Midi and midilike + FILE_MOD, ///< MOD and modlike FMT_NETWORK, ///< Network http stream } FILE_audioformat; diff --git a/src/formats/xmp.cpp b/src/formats/xmp.cpp new file mode 100644 index 0000000..12b100c --- /dev/null +++ b/src/formats/xmp.cpp @@ -0,0 +1,109 @@ +/* LimePlayer3DS FOSS graphcal music player for the Nintendo 3DS. +* Copyright (C) 2018-2019 LimePlayer Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ +#include + +#include +#include +#include + +#include "xmp.hpp" + +XmpDecoder::XmpDecoder(const char* filename) : Decoder("Xmp") { + ctx = xmp_create_context(); + + if (xmp_load_module(ctx, filename) < 0) + return; + + if (xmp_start_player(ctx, XMPSAMPLERATE, 0) < 0) + return; + + xmp_get_module_info(ctx, &mi); + decoderName = mi.mod->type; + mIsInit = true; +} + +XmpDecoder::~XmpDecoder(void) { + xmp_end_player(ctx); + xmp_release_module(ctx); + xmp_free_context(ctx); + mIsInit = false; +} + +void XmpDecoder::GetInfo(metaInfo_t* Meta) { + Meta->Title.assign(mi.mod->name); + Meta->Artist.assign("(No Author-Mod)"); +} + +uint32_t XmpDecoder::Position(void) { + struct xmp_frame_info fi; + xmp_get_frame_info(ctx, &fi); + return fi.pos; +} + +uint32_t XmpDecoder::Length(void) { + return mi.mod->len; +} + +void XmpDecoder::Seek(uint32_t location) +{ + if(location > (uint32_t)mi.mod->len) { + return; + } + xmp_set_position(ctx, location); +} + +void XmpDecoder::Next(void) +{ + Seek(Position() + 1); +} + +void XmpDecoder::Previous(void) +{ + Seek(Position() - 1); +} + +/** + * Decode part of open module file. + * + * \param buffer Decoded output. + * \return Samples read for each channel. + */ +uint32_t XmpDecoder::Decode(void* buffer) +{ + xmp_play_buffer(ctx, buffer, buffSize, 0); + return buffSize/sizeof(int16_t); +} + +/** + * Get sampling rate of module file. + * + * \return Sampling rate. + */ +uint32_t XmpDecoder::Samplerate(void) +{ + return XMPSAMPLERATE; +} + +uint32_t XmpDecoder::Buffsize(void) +{ + return buffSize; +} + +int XmpDecoder::Channels(void) +{ + return 2; +} diff --git a/src/formats/xmp.hpp b/src/formats/xmp.hpp new file mode 100644 index 0000000..7082108 --- /dev/null +++ b/src/formats/xmp.hpp @@ -0,0 +1,43 @@ +#ifndef DECODER_XMP_H +#define DECODER_XMP_H + +#include + +#include "player.hpp" + +#define XMPSAMPLERATE 32000 +#define SAMPLESPERBUF 4096 +#define BYTESPERSAMPLE 4 + +class XmpDecoder : public Decoder { + public: + XmpDecoder(const char* filename); + + ~XmpDecoder(void); + + void GetInfo(metaInfo_t* Meta) override; + + uint32_t Position(void) override; + + uint32_t Length(void) override; + + void Seek(uint32_t location) override; + + void Next(void) override; + + void Previous(void) override; + + uint32_t Decode(void* buffer) override; + + uint32_t Samplerate(void) override; + + uint32_t Buffsize(void) override; + + int Channels(void) override; + private: + xmp_context ctx; + struct xmp_module_info mi; + size_t buffSize = SAMPLESPERBUF*BYTESPERSAMPLE; +}; + +#endif diff --git a/src/gui/menus/PlayerMenu.cpp b/src/gui/menus/PlayerMenu.cpp index 8d41b08..5eeb74e 100644 --- a/src/gui/menus/PlayerMenu.cpp +++ b/src/gui/menus/PlayerMenu.cpp @@ -227,12 +227,12 @@ void PlayerMenu::update(touchPosition* touch) if (kDown & KEY_RIGHT) { if(PlayerInterface::IsPlaying()) - PlayerInterface::SeekSectionTime(PlayerInterface::GetCurrentTime()+15); + PlayerInterface::NextSection(); } if (kDown & KEY_LEFT) { if(PlayerInterface::IsPlaying()) - PlayerInterface::SeekSectionTime(PlayerInterface::GetCurrentTime()-15); + PlayerInterface::PreviousSection(); } if(kDown & KEY_A) { @@ -265,7 +265,7 @@ void PlayerMenu::update(touchPosition* touch) if (kDown & KEY_TOUCH) { if (PlayerInterface::IsPlaying()) { if (m_buttons[CON_REWIND].Update(touch->px, touch->py)) - PlayerInterface::SeekSectionTime(PlayerInterface::GetCurrentTime()-15); + PlayerInterface::PreviousSection(); else if (m_buttons[CON_STOP].Update(touch->px, touch->py)) exitPlayback(); else if (m_buttons[CON_PLAYPAUSE].Update(touch->px, touch->py)) @@ -273,7 +273,7 @@ void PlayerMenu::update(touchPosition* touch) else if (m_buttons[CON_SKIP].Update(touch->px, touch->py)) PlayerInterface::SkipPlayback(); else if (m_buttons[CON_FASTFOWARD].Update(touch->px, touch->py)) - PlayerInterface::SeekSectionTime(PlayerInterface::GetCurrentTime()+15); + PlayerInterface::NextSection(); } int seekto = m_progressbars[0].SeekByClick(touch->px, touch->py); diff --git a/src/player.cpp b/src/player.cpp index 19d05fe..3001a42 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -34,6 +34,7 @@ #include "formats/opus.hpp" #include "formats/vorbis.hpp" #include "formats/wav.hpp" +#include "formats/xmp.hpp" #include "formats/netfmt.hpp" #include "transport.hpp" @@ -135,17 +136,6 @@ uint32_t PlayerInterface::GetTotalLength(void) { return 0; } -/* - * Returns the estimated total number of seconds in the file. - * \return Estimated total seconds. - */ -uint32_t PlayerInterface::GetTotalTime(void) { - if (!stop && decoder) - return PlayerInterface::GetTotalLength()/decoder->Samplerate(); - - return 0; -} - /* * Returns the current sample. * \return Current sample. @@ -158,14 +148,31 @@ uint32_t PlayerInterface::GetCurrentPos(void) { } /* - * Returns the estimated number of seconds elapsed. - * \return Current location in file in seconds. + * Seeks to the next section in the file, + * although it is not meant to be called directly + * it is avaliable to be used by other functions. */ -uint32_t PlayerInterface::GetCurrentTime(void) { - if (!stop && decoder) - return PlayerInterface::GetCurrentPos()/decoder->Samplerate(); +void PlayerInterface::NextSection(void) { + if (!stop && decoder) { + bool oldstate = ndspChnIsPaused(MUSIC_CHANNEL); + ndspChnSetPaused(MUSIC_CHANNEL, true); //Pause playback... + decoder->Next(); + ndspChnSetPaused(MUSIC_CHANNEL, oldstate); //once the seeking is done, playback can continue. + } +} - return 0; +/* + * Seeks to the previous section in the file, + * although it is not meant to be called directly + * it is avaliable to be used by other functions. + */ +void PlayerInterface::PreviousSection(void) { + if (!stop && decoder) { + bool oldstate = ndspChnIsPaused(MUSIC_CHANNEL); + ndspChnSetPaused(MUSIC_CHANNEL, true); //Pause playback... + decoder->Previous(); + ndspChnSetPaused(MUSIC_CHANNEL, oldstate); //once the seeking is done, playback can continue. + } } /* @@ -190,15 +197,6 @@ void PlayerInterface::SeekSectionPercent(uint32_t percent) { PlayerInterface::SeekSection((PlayerInterface::GetTotalLength() * (percent / 100.0f))); } -/* - * Estimates the elapsed samples for a given time in seconds. - */ -void PlayerInterface::SeekSectionTime(int time) { - if (time < 0) - time = 0; - PlayerInterface::SeekSection(time * decoder->Samplerate()); -} - /** * Returns Decoder name. * \return String of name. @@ -361,6 +359,12 @@ std::unique_ptr Player::GetFormat(const playbackInfo_t* playbackInfo, i if (mididec->GetIsInit()) return mididec; } + else if (filetype == FILE_MOD) { + DEBUG("Attempting to load the Xmp decoder.\n"); + auto xmpdec = std::make_unique(playbackInfo->filepath.c_str()); + if (xmpdec->GetIsInit()) + return xmpdec; + } else if (filetype == FMT_NETWORK) { DEBUG("Attempting to load the Network decoder.\n"); auto netdec = Netfmt::GetFormat(playbackInfo->filepath.c_str(), transport); diff --git a/src/player.hpp b/src/player.hpp index 4583078..ddc7cea 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -52,10 +52,7 @@ class Decoder { const char* GetErrInfo(void) {return mErrInfo;} std::string GetDecoderName(void) { - if (decoderName) - return decoderName; - else - return ""; + return decoderName; } // Allow the decoder to specify if the medadata needs to be updated. @@ -63,6 +60,17 @@ class Decoder { virtual void UpdateInfo(metaInfo_t* Meta) {}; + // Allow the decoder to implement custom next/previous behaviour. + virtual void Next() { + uint32_t time = (Position() / Samplerate()); + Seek((time + 15) * Samplerate()); + } + + virtual void Previous() { + uint32_t time = (Position() / Samplerate()); + Seek((time < 15 ? 0 : time - 15) * Samplerate()); + } + virtual void GetInfo(metaInfo_t* Meta) = 0; virtual uint32_t Position(void) = 0; @@ -84,8 +92,7 @@ class Decoder { const char* mErrInfo; - private: - const char* decoderName = "Unknown"; + std::string decoderName = "Unknown"; }; namespace PlayerInterface { @@ -103,18 +110,16 @@ namespace PlayerInterface { uint32_t GetTotalLength(void); - uint32_t GetTotalTime(void); - uint32_t GetCurrentPos(void); - uint32_t GetCurrentTime(void); + void NextSection(void); + + void PreviousSection(void); void SeekSection(uint32_t location); void SeekSectionPercent(uint32_t percent); - void SeekSectionTime(int time); - std::string GetDecoderName(void); }