Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for module playback via libxmp #26

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
5 changes: 5 additions & 0 deletions src/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <xmp.h>

#include "file.hpp"
#include "formats/vorbis.hpp"
Expand Down Expand Up @@ -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.
}

Expand Down
1 change: 1 addition & 0 deletions src/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
109 changes: 109 additions & 0 deletions src/formats/xmp.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <memory>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#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;
}
43 changes: 43 additions & 0 deletions src/formats/xmp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#ifndef DECODER_XMP_H
#define DECODER_XMP_H

#include <xmp.h>

#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
8 changes: 4 additions & 4 deletions src/gui/menus/PlayerMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -265,15 +265,15 @@ 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))
PlayerInterface::TogglePlayback();
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);
Expand Down
56 changes: 30 additions & 26 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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.
}
}

/*
Expand All @@ -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.
Expand Down Expand Up @@ -361,6 +359,12 @@ std::unique_ptr<Decoder> 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<XmpDecoder>(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);
Expand Down
27 changes: 16 additions & 11 deletions src/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,25 @@ 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.
virtual bool AllowUpdateInfo() {return false;}

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;
Expand All @@ -84,8 +92,7 @@ class Decoder {

const char* mErrInfo;

private:
const char* decoderName = "Unknown";
std::string decoderName = "Unknown";
};

namespace PlayerInterface {
Expand All @@ -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);
}

Expand Down