mirror of
https://github.com/headshot2017/libadx-nds.git
synced 2025-06-18 17:05:40 -04:00
first commit
This commit is contained in:
commit
77cebdcc56
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
*.elf
|
||||
*.nds
|
26
LICENSE
Normal file
26
LICENSE
Normal file
@ -0,0 +1,26 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (C) 2011-2013 Josh 'PH3NOM' Pearson
|
||||
Copyright (C) 2024 The KOS Team and contributors
|
||||
Copyright (C) 2024 Headshotnoby
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||
|
||||
BLOCKSDS ?= /opt/blocksds/core
|
||||
|
||||
# User config
|
||||
|
||||
NAME := libadx-nds
|
||||
GAME_TITLE := libadx-nds test
|
||||
NITROFSDIR := nitro
|
||||
|
||||
include $(BLOCKSDS)/sys/default_makefiles/rom_arm9arm7/Makefile
|
16
Makefile.arm7
Normal file
16
Makefile.arm7
Normal file
@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||
|
||||
BLOCKSDS ?= /opt/blocksds/core
|
||||
|
||||
DEFINES := -DARM7
|
||||
SOURCEDIRS := arm7/source
|
||||
INCLUDEDIRS := common
|
||||
|
||||
LIBS := -lnds7 -ldswifi7 -lmm7
|
||||
LIBDIRS := $(BLOCKSDS)/libs/libnds \
|
||||
$(BLOCKSDS)/libs/dswifi \
|
||||
$(BLOCKSDS)/libs/maxmod
|
||||
|
||||
include $(BLOCKSDS)/sys/default_makefiles/rom_arm9arm7/Makefile.arm7
|
11
Makefile.arm9
Normal file
11
Makefile.arm9
Normal file
@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||
|
||||
BLOCKSDS ?= /opt/blocksds/core
|
||||
|
||||
DEFINES := -DARM9
|
||||
SOURCEDIRS := arm9/source
|
||||
INCLUDEDIRS := common
|
||||
|
||||
include $(BLOCKSDS)/sys/default_makefiles/rom_arm9arm7/Makefile.arm9
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# libadx-nds
|
||||
|
||||
ADX decoder/player for Nintendo DS homebrew, written using [BlocksDS](https://github.com/blocksds/sdk)
|
||||
|
||||
## Information
|
||||
|
||||
When playing an ADX file, it is decoded on the ARM7, allowing you to run game logic on the ARM9 uninterrupted.
|
||||
|
||||
The ADX quality can go up to 48000 Hz 128 kbps, with no lag.<br/>
|
||||
Higher kbps quality may be possible, but it has not been tested.
|
||||
|
||||
## Credits
|
||||
* LibADX Dreamcast library (c)2011-2013 Josh PH3NOM Pearson
|
||||
* Built on top of [NDS Helix-MP3 decoder](https://adshomebrewersdiary.blogspot.com/2012/06/mp3-streaming-on-arm7.html) code by sverx
|
447
arm7/source/adx/adx_7.c
Normal file
447
arm7/source/adx/adx_7.c
Normal file
@ -0,0 +1,447 @@
|
||||
// libadx-nds library
|
||||
// by headshot2017 (Headshotnoby)
|
||||
// LibADX Dreamcast library (c)2012 Josh PH3NOM Pearson
|
||||
// Built on top of NDS Helix-MP3 decoder code by sverx (https://adshomebrewersdiary.blogspot.com/2012/06/mp3-streaming-on-arm7.html)
|
||||
|
||||
#include <nds.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include "libadx.h"
|
||||
|
||||
int getFreeChannel();
|
||||
|
||||
volatile adx_player *adx;
|
||||
volatile adx_player_state adx_state;
|
||||
static int adx_channelLeft;
|
||||
static int adx_channelRight;
|
||||
static int adx_volume;
|
||||
static int pcm_size;
|
||||
static u8* adx_readPtr;
|
||||
|
||||
static DSTIME ds_sound_start;
|
||||
static DSTIME soundtime;
|
||||
static DSTIME paintedtime;
|
||||
|
||||
|
||||
/* Convert ADX samples to PCM samples */
|
||||
static void adx_to_pcm(short *out,unsigned char *in,PREV *prev)
|
||||
{
|
||||
int scale = ((in[0]<<8)|(in[1]));
|
||||
int i;
|
||||
int s0,s1,s2,d;
|
||||
|
||||
in+=2;
|
||||
s1 = prev->s1;
|
||||
s2 = prev->s2;
|
||||
for(i=0;i<16;i++) {
|
||||
d = in[i]>>4;
|
||||
if (d&8) d-=16;
|
||||
s0 = (BASEVOL*d*scale + 0x7298*s1 - 0x3350*s2)>>14;
|
||||
if (s0>32767) s0=32767;
|
||||
else if (s0<-32768) s0=-32768;
|
||||
*out++=s0;
|
||||
s2 = s1;
|
||||
s1 = s0;
|
||||
|
||||
d = in[i]&15;
|
||||
if (d&8) d-=16;
|
||||
s0 = (BASEVOL*d*scale + 0x7298*s1 - 0x3350*s2)>>14;
|
||||
if (s0>32767) s0=32767;
|
||||
else if (s0<-32768) s0=-32768;
|
||||
*out++=s0;
|
||||
s2 = s1;
|
||||
s1 = s0;
|
||||
}
|
||||
prev->s1 = s1;
|
||||
prev->s2 = s2;
|
||||
|
||||
}
|
||||
|
||||
static DSTIME ds_time()
|
||||
{
|
||||
u16 time1 = TIMER1_DATA;
|
||||
u32 time2 = TIMER2_DATA;
|
||||
|
||||
return (time2 << 16) + time1;
|
||||
}
|
||||
|
||||
static void ds_set_timer(int rate)
|
||||
{
|
||||
if (rate == 0)
|
||||
{
|
||||
TIMER_CR(0) = 0;
|
||||
TIMER_CR(1) = 0;
|
||||
TIMER_CR(2) = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
TIMER_DATA(0) = 0x10000 - (0x1000000 / rate) * 2;
|
||||
TIMER_CR(0) = TIMER_ENABLE | TIMER_DIV_1;
|
||||
TIMER_DATA(1) = 0;
|
||||
TIMER_CR(1) = TIMER_ENABLE | TIMER_CASCADE | TIMER_DIV_1;
|
||||
TIMER_DATA(2) = 0;
|
||||
TIMER_CR(2) = TIMER_ENABLE | TIMER_CASCADE | TIMER_DIV_1;
|
||||
}
|
||||
}
|
||||
|
||||
static DSTIME ds_sample_pos()
|
||||
{
|
||||
DSTIME v;
|
||||
|
||||
v = (ds_time() - ds_sound_start);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int adx_frame()
|
||||
{
|
||||
// Do not decode any frames if the decoder
|
||||
// is asking for more data from the file.
|
||||
if (adx->flag == 1)
|
||||
return 0;
|
||||
|
||||
unsigned char *left = (unsigned char*)adx->audioLeft;
|
||||
unsigned char *right = (unsigned char*)adx->audioRight;
|
||||
unsigned char adx_buf[ADX_HDR_SIZE];
|
||||
short outbuf[32*2];
|
||||
int wsize;
|
||||
int pcm_samples = adx->ADX_Info.samples;
|
||||
|
||||
if (adx->ADX_Info.channels==1)
|
||||
{
|
||||
if (pcm_samples)
|
||||
{
|
||||
// If there is room in PCM buffer, decode next chunk of ADX samples
|
||||
if (pcm_size < ADX_AUDIO_BUFFER_SIZE)
|
||||
{
|
||||
// Read the current chunk
|
||||
memcpy(adx_buf, adx_readPtr, adx->ADX_Info.chunk_size);
|
||||
adx_readPtr += adx->ADX_Info.chunk_size;
|
||||
|
||||
// Convert ADX chunk to PCM
|
||||
adx_to_pcm(outbuf, adx_buf, adx->prev);
|
||||
if (pcm_samples>32) wsize=32; else wsize = pcm_samples;
|
||||
pcm_samples -= wsize;
|
||||
|
||||
// Copy the deocded samples to sample buffer
|
||||
memcpy(left+pcm_size, outbuf, wsize*2);
|
||||
pcm_size += wsize*2;
|
||||
paintedtime += wsize;
|
||||
}
|
||||
else
|
||||
pcm_size = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (adx->ADX_Info.channels==2)
|
||||
{
|
||||
if (pcm_samples)
|
||||
{
|
||||
// If there is room in PCM buffer, decode next chunk of ADX samples
|
||||
if (pcm_size < ADX_AUDIO_BUFFER_SIZE)
|
||||
{
|
||||
memcpy(adx_buf, adx_readPtr, adx->ADX_Info.chunk_size*2);
|
||||
adx_readPtr += adx->ADX_Info.chunk_size*2;
|
||||
|
||||
adx_to_pcm(outbuf, adx_buf, adx->prev);
|
||||
adx_to_pcm(outbuf+32, adx_buf+adx->ADX_Info.chunk_size, adx->prev+1);
|
||||
if (pcm_samples>32) wsize=32; else wsize = pcm_samples;
|
||||
pcm_samples -= wsize;
|
||||
|
||||
memcpy(left+pcm_size, outbuf, wsize*2);
|
||||
memcpy(right+pcm_size, outbuf+32, wsize*2);
|
||||
pcm_size += wsize*2;
|
||||
paintedtime += wsize;
|
||||
}
|
||||
else
|
||||
pcm_size = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adx_frames(DSTIME endtime, u8 firstFrames)
|
||||
{
|
||||
while (paintedtime < endtime)
|
||||
{
|
||||
if (firstFrames && adx->flag == 1)
|
||||
break;
|
||||
|
||||
adx_frame();
|
||||
|
||||
if (adx->flag == 4) break; // non-looping music ended, stop here
|
||||
|
||||
// check if we moved onto the 2nd file data buffer, if so move it to the 1st one and request a refill
|
||||
if (adx_readPtr > (adx->buffer + ADX_FILE_BUFFER_SIZE + (ADX_FILE_BUFFER_SIZE>>1)))
|
||||
{
|
||||
adx_readPtr -= ADX_FILE_BUFFER_SIZE;
|
||||
memcpy((void *)adx_readPtr, (void *)(adx_readPtr + ADX_FILE_BUFFER_SIZE), ADX_FILE_BUFFER_SIZE - (adx_readPtr-adx->buffer));
|
||||
adx->flag = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int adx_playing()
|
||||
{
|
||||
DSTIME endtime;
|
||||
|
||||
soundtime = ds_sample_pos();
|
||||
|
||||
// check to make sure that we haven't overshot
|
||||
if (paintedtime < soundtime)
|
||||
paintedtime = soundtime;
|
||||
|
||||
// mix ahead of current position
|
||||
endtime = soundtime + (adx->ADX_Info.rate>>4);
|
||||
|
||||
adx_frames(endtime, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void adx_pause()
|
||||
{
|
||||
if (adx == 0 || adx_channelLeft == -1)
|
||||
return;
|
||||
|
||||
adx->flag = 0;
|
||||
ds_set_timer(0);
|
||||
SCHANNEL_CR(adx_channelLeft) = 0;
|
||||
SCHANNEL_CR(adx_channelRight) = 0;
|
||||
adx_channelLeft = -1;
|
||||
adx_state = ADX_PAUSED;
|
||||
}
|
||||
|
||||
static int adx_resume()
|
||||
{
|
||||
if (adx == 0 || adx_channelLeft != -1)
|
||||
return 0;
|
||||
|
||||
adx->flag = 0;
|
||||
paintedtime = 0;
|
||||
pcm_size = 0;
|
||||
memset((void *)adx->audioLeft, 0, ADX_AUDIO_BUFFER_SIZE);
|
||||
memset((void *)adx->audioRight, 0, ADX_AUDIO_BUFFER_SIZE);
|
||||
adx_frames(ADX_AUDIO_BUFFER_SAMPS>>1, 1);
|
||||
|
||||
adx_channelLeft = getFreeChannel();
|
||||
|
||||
SCHANNEL_SOURCE(adx_channelLeft) = (u32)adx->audioLeft;
|
||||
SCHANNEL_REPEAT_POINT(adx_channelLeft) = 0;
|
||||
SCHANNEL_LENGTH(adx_channelLeft) = (ADX_AUDIO_BUFFER_SIZE)>>2;
|
||||
SCHANNEL_TIMER(adx_channelLeft) = 0x10000 - (0x1000000 / adx->ADX_Info.rate);
|
||||
|
||||
if (adx->ADX_Info.channels == 1)
|
||||
{
|
||||
// Mono
|
||||
SCHANNEL_CR(adx_channelLeft) = SCHANNEL_ENABLE | SOUND_VOL(adx_volume) | SOUND_PAN(64) | (SOUND_FORMAT_16BIT) | (SOUND_REPEAT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stereo
|
||||
// "lock" (silent) this channel, so that next getFreeChannel call gives a different one...
|
||||
SCHANNEL_CR(adx_channelLeft) = SCHANNEL_ENABLE | SOUND_VOL(0) | SOUND_PAN(0) | (SOUND_FORMAT_16BIT) | (SOUND_REPEAT);
|
||||
adx_channelRight = getFreeChannel();
|
||||
SCHANNEL_CR(adx_channelLeft) = 0;
|
||||
|
||||
SCHANNEL_SOURCE(adx_channelRight) = (u32)adx->audioRight;
|
||||
SCHANNEL_REPEAT_POINT(adx_channelRight) = 0;
|
||||
SCHANNEL_LENGTH(adx_channelRight) = (ADX_AUDIO_BUFFER_SIZE)>>2;
|
||||
SCHANNEL_TIMER(adx_channelRight) = 0x10000 - (0x1000000 / adx->ADX_Info.rate);
|
||||
|
||||
SCHANNEL_CR(adx_channelLeft) = SCHANNEL_ENABLE | SOUND_VOL(adx_volume) | SOUND_PAN(0) | (SOUND_FORMAT_16BIT) | (SOUND_REPEAT);
|
||||
SCHANNEL_CR(adx_channelRight) = SCHANNEL_ENABLE | SOUND_VOL(adx_volume) | SOUND_PAN(127) | (SOUND_FORMAT_16BIT) | (SOUND_REPEAT);
|
||||
}
|
||||
|
||||
ds_set_timer(adx->ADX_Info.rate);
|
||||
ds_sound_start = ds_time();
|
||||
|
||||
adx_state = ADX_PLAYING;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void adx_stop()
|
||||
{
|
||||
if (!adx) return;
|
||||
adx = 0;
|
||||
|
||||
ds_set_timer(0);
|
||||
SCHANNEL_CR(adx_channelLeft) = 0;
|
||||
SCHANNEL_CR(adx_channelRight) = 0;
|
||||
//free((void *)&SCHANNEL_SOURCE(adx_channelLeft));
|
||||
//free((void *)&SCHANNEL_SOURCE(adx_channelRight));
|
||||
SCHANNEL_SOURCE(adx_channelLeft) = 0;
|
||||
SCHANNEL_SOURCE(adx_channelRight) = 0;
|
||||
adx_channelLeft = -1;
|
||||
adx_state = ADX_IDLE;
|
||||
}
|
||||
|
||||
static void adx_starting()
|
||||
{
|
||||
adx_readPtr = adx->buffer;
|
||||
|
||||
adx_resume();
|
||||
fifoSendValue32(FIFO_USER_01, 1);
|
||||
}
|
||||
|
||||
static void adx_resuming()
|
||||
{
|
||||
if (adx == 0)
|
||||
{
|
||||
adx_state = ADX_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
adx_resume();
|
||||
}
|
||||
|
||||
static void adx_pausing()
|
||||
{
|
||||
if (adx == 0)
|
||||
{
|
||||
adx_state = ADX_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
adx_pause();
|
||||
}
|
||||
|
||||
static void adx_stopping()
|
||||
{
|
||||
if (adx == 0)
|
||||
{
|
||||
adx_state = ADX_IDLE;
|
||||
fifoSendValue32(FIFO_USER_01, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
adx_stop();
|
||||
fifoSendValue32(FIFO_USER_01, 1);
|
||||
}
|
||||
|
||||
static void adx_restarting()
|
||||
{
|
||||
if (adx == 0)
|
||||
{
|
||||
adx_state = ADX_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
adx_pause();
|
||||
|
||||
adx_readPtr = adx->buffer;
|
||||
|
||||
int i;
|
||||
for (i=0; i<2; i++)
|
||||
{
|
||||
adx->prev[i].s1 = 0;
|
||||
adx->prev[i].s2 = 0;
|
||||
}
|
||||
|
||||
adx_resume();
|
||||
}
|
||||
|
||||
static void adx_set_volume(int volume)
|
||||
{
|
||||
// clamp
|
||||
volume =
|
||||
(volume < 0) ? 0 :
|
||||
(volume > 127) ? 127 :
|
||||
volume;
|
||||
|
||||
adx_volume = volume;
|
||||
|
||||
if (adx_channelLeft == -1)
|
||||
{
|
||||
fifoSendValue32(FIFO_USER_01, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
SCHANNEL_CR(adx_channelLeft) = (SCHANNEL_CR(adx_channelLeft) & ~0xFF) | volume;
|
||||
if (adx->ADX_Info.channels == 2)
|
||||
SCHANNEL_CR(adx_channelRight) = (SCHANNEL_CR(adx_channelRight) & ~0xFF) | volume;
|
||||
|
||||
fifoSendValue32(FIFO_USER_01, 1);
|
||||
}
|
||||
|
||||
void adx_update()
|
||||
{
|
||||
switch(adx_state)
|
||||
{
|
||||
case ADX_STARTING:
|
||||
adx_starting();
|
||||
break;
|
||||
case ADX_PLAYING:
|
||||
adx_playing();
|
||||
break;
|
||||
case ADX_PAUSING:
|
||||
adx_pausing();
|
||||
break;
|
||||
case ADX_RESUMING:
|
||||
adx_resuming();
|
||||
break;
|
||||
case ADX_STOPPING:
|
||||
adx_stopping();
|
||||
break;
|
||||
case ADX_RESTARTING:
|
||||
adx_restarting();
|
||||
break;
|
||||
case ADX_PAUSED:
|
||||
case ADX_IDLE:
|
||||
case ADX_ERROR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void adx_DataHandler(int bytes, void *user_data)
|
||||
{
|
||||
adx_msg msg;
|
||||
|
||||
fifoGetDatamsg(FIFO_USER_01, bytes, (u8*)&msg);
|
||||
|
||||
switch(msg.type)
|
||||
{
|
||||
case ADX_MSG_START:
|
||||
adx = msg.player;
|
||||
adx_state = ADX_STARTING;
|
||||
break;
|
||||
|
||||
case ADX_MSG_PAUSE:
|
||||
adx_state = ADX_PAUSING;
|
||||
break;
|
||||
|
||||
case ADX_MSG_RESUME:
|
||||
adx_state = ADX_RESUMING;
|
||||
break;
|
||||
|
||||
case ADX_MSG_STOP:
|
||||
adx_state = ADX_STOPPING;
|
||||
break;
|
||||
|
||||
case ADX_MSG_RESTART:
|
||||
adx_state = ADX_RESTARTING;
|
||||
break;
|
||||
|
||||
case ADX_MSG_VOLUME:
|
||||
adx_set_volume(msg.volume);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int adx_init()
|
||||
{
|
||||
adx = 0;
|
||||
adx_state = ADX_IDLE;
|
||||
adx_channelLeft = -1;
|
||||
adx_volume = 127;
|
||||
|
||||
fifoSetDatamsgHandler(FIFO_USER_01, adx_DataHandler, 0);
|
||||
|
||||
return 1;
|
||||
}
|
87
arm7/source/main.c
Normal file
87
arm7/source/main.c
Normal file
@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
//
|
||||
// Copyright (C) 2005 Michael Noland (joat)
|
||||
// Copyright (C) 2005 Jason Rogers (Dovoto)
|
||||
// Copyright (C) 2005-2015 Dave Murphy (WinterMute)
|
||||
// Copyright (C) 2023 Antonio Niño Díaz
|
||||
|
||||
// Default ARM7 core
|
||||
|
||||
#include <dswifi7.h>
|
||||
#include <nds.h>
|
||||
#include <maxmod7.h>
|
||||
|
||||
#include "libadx.h"
|
||||
|
||||
volatile bool exit_loop = false;
|
||||
|
||||
void power_button_callback(void)
|
||||
{
|
||||
exit_loop = true;
|
||||
}
|
||||
|
||||
void vblank_handler(void)
|
||||
{
|
||||
inputGetAndSend();
|
||||
Wifi_Update();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Initialize sound hardware
|
||||
enableSound();
|
||||
|
||||
// Read user information from the firmware (name, birthday, etc)
|
||||
readUserSettings();
|
||||
|
||||
// Stop LED blinking
|
||||
ledBlink(0);
|
||||
|
||||
// Using the calibration values read from the firmware with
|
||||
// readUserSettings(), calculate some internal values to convert raw
|
||||
// coordinates into screen coordinates.
|
||||
touchInit();
|
||||
|
||||
irqInit();
|
||||
fifoInit();
|
||||
|
||||
installSoundFIFO();
|
||||
installSystemFIFO(); // Sleep mode, storage, firmware...
|
||||
installWifiFIFO();
|
||||
if (isDSiMode())
|
||||
installCameraFIFO();
|
||||
|
||||
// Initialize Maxmod. It uses timer 0 internally.
|
||||
mmInstall(FIFO_MAXMOD);
|
||||
|
||||
// This sets a callback that is called when the power button in a DSi
|
||||
// console is pressed. It has no effect in a DS.
|
||||
setPowerButtonCB(power_button_callback);
|
||||
|
||||
// Read current date from the RTC and setup an interrupt to update the time
|
||||
// regularly. The interrupt simply adds one second every time, it doesn't
|
||||
// read the date. Reading the RTC is very slow, so it's a bad idea to do it
|
||||
// frequently.
|
||||
initClockIRQTimer(3);
|
||||
|
||||
// Now that the FIFO is setup we can start sending input data to the ARM9.
|
||||
irqSet(IRQ_VBLANK, vblank_handler);
|
||||
irqEnable(IRQ_VBLANK);
|
||||
|
||||
adx_init();
|
||||
|
||||
while (!exit_loop)
|
||||
{
|
||||
adx_update();
|
||||
|
||||
const uint16_t key_mask = KEY_SELECT | KEY_START | KEY_L | KEY_R;
|
||||
uint16_t keys_pressed = ~REG_KEYINPUT;
|
||||
|
||||
if ((keys_pressed & key_mask) == key_mask)
|
||||
exit_loop = true;
|
||||
|
||||
swiWaitForVBlank();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
271
arm9/source/adx/adx_9.c
Normal file
271
arm9/source/adx/adx_9.c
Normal file
@ -0,0 +1,271 @@
|
||||
// libadx-nds library
|
||||
// by headshot2017 (Headshotnoby)
|
||||
// LibADX Dreamcast library (c)2012 Josh PH3NOM Pearson
|
||||
// Built on top of NDS Helix-MP3 decoder code by sverx (https://adshomebrewersdiary.blogspot.com/2012/06/mp3-streaming-on-arm7.html)
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <nds.h>
|
||||
|
||||
#include "libadx.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static FILE* adx_in;
|
||||
static u8* adx_buffer;
|
||||
static u16* adx_audioLeft;
|
||||
static u16* adx_audioRight;
|
||||
volatile adx_player* adx;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int read_be16(unsigned char *buf) /* ADX File Format is Big Endian */
|
||||
{
|
||||
return (buf[0]<<8)|buf[1];
|
||||
}
|
||||
|
||||
static long read_be32(unsigned char *buf)
|
||||
{
|
||||
return (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
|
||||
}
|
||||
|
||||
// Read and parse the ADX File header then seek to the begining sample offset
|
||||
static int adx_parse( unsigned char *buf )
|
||||
{
|
||||
fseek( adx_in, 0, SEEK_SET ); // Read the ADX Header into memory
|
||||
fread( buf, 1, ADX_HDR_SIZE, adx_in );
|
||||
if(buf[0]!=ADX_HDR_SIG ) return -1; // Check ADX File Signature
|
||||
|
||||
// Parse the ADX File header
|
||||
adx->ADX_Info.sample_offset = read_be16(buf+ADX_ADDR_START)-2;
|
||||
adx->ADX_Info.chunk_size = buf[ADX_ADDR_CHUNK];
|
||||
adx->ADX_Info.channels = buf[ADX_ADDR_CHAN];
|
||||
adx->ADX_Info.rate = read_be32(buf+ADX_ADDR_RATE);
|
||||
adx->ADX_Info.samples = read_be32(buf+ADX_ADDR_SAMP);
|
||||
adx->ADX_Info.loop_type = buf[ADX_ADDR_TYPE];
|
||||
|
||||
// Two known variations for possible loop informations: type 3 and type 4
|
||||
if( adx->ADX_Info.loop_type == 3 )
|
||||
adx->ADX_Info.loop = read_be32(buf+ADX_ADDR_LOOP);
|
||||
else if( adx->ADX_Info.loop_type == 4 )
|
||||
adx->ADX_Info.loop = read_be32(buf+ADX_ADDR_LOOP+0x0c);
|
||||
if( adx->ADX_Info.loop > 1 || adx->ADX_Info.loop < 0 ) // Invalid header check
|
||||
adx->ADX_Info.loop = 0;
|
||||
if( adx->ADX_Info.loop && adx->ADX_Info.loop_type == 3 )
|
||||
{
|
||||
adx->ADX_Info.loop_samp_start = read_be32(buf+ADX_ADDR_SAMP_START);
|
||||
adx->ADX_Info.loop_start = read_be32(buf+ADX_ADDR_BYTE_START);
|
||||
adx->ADX_Info.loop_samp_end = read_be32(buf+ADX_ADDR_SAMP_END);
|
||||
adx->ADX_Info.loop_end = read_be32(buf+ADX_ADDR_BYTE_END);
|
||||
}
|
||||
else if( adx->ADX_Info.loop && adx->ADX_Info.loop_type == 4 )
|
||||
{
|
||||
adx->ADX_Info.loop_samp_start = read_be32(buf+ADX_ADDR_SAMP_START+0x0c);
|
||||
adx->ADX_Info.loop_start = read_be32(buf+ADX_ADDR_BYTE_START+0x0c);
|
||||
adx->ADX_Info.loop_samp_end = read_be32(buf+ADX_ADDR_SAMP_END+0x0c);
|
||||
adx->ADX_Info.loop_end = read_be32(buf+ADX_ADDR_BYTE_END+0x0c);
|
||||
}
|
||||
if( adx->ADX_Info.loop )
|
||||
adx->ADX_Info.loop_samples = adx->ADX_Info.loop_samp_end-adx->ADX_Info.loop_samp_start;
|
||||
|
||||
fseek( adx_in, adx->ADX_Info.sample_offset, SEEK_SET ); // CRI File Signature
|
||||
fread( buf, 1, 6, adx_in );
|
||||
|
||||
if ( memcmp(buf, "(c)CRI", 6) )
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void *uncached_malloc(size_t count)
|
||||
{
|
||||
void *p = malloc(count);
|
||||
return ((p == 0) ? 0 : memUncached(p));
|
||||
}
|
||||
|
||||
int adx_init()
|
||||
{
|
||||
adx_in = 0;
|
||||
adx = (adx_player*)uncached_malloc(sizeof(adx_player));
|
||||
adx_buffer = (u8*)uncached_malloc(ADX_FILE_BUFFER_SIZE*2);
|
||||
adx_audioLeft = (u16*)malloc(ADX_AUDIO_BUFFER_SIZE);
|
||||
adx_audioRight = (u16*)malloc(ADX_AUDIO_BUFFER_SIZE);
|
||||
if (!adx || !adx_buffer || !adx_audioLeft || !adx_audioRight)
|
||||
return 0;
|
||||
|
||||
memset((void*)adx, 0, sizeof(adx_player));
|
||||
memset((void*)adx_buffer, 0, ADX_FILE_BUFFER_SIZE*2);
|
||||
memset((void*)adx_audioLeft, 0, ADX_AUDIO_BUFFER_SIZE);
|
||||
memset((void*)adx_audioRight, 0, ADX_AUDIO_BUFFER_SIZE);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void adx_update()
|
||||
{
|
||||
if (!adx) return;
|
||||
|
||||
int n;
|
||||
|
||||
switch(adx->flag)
|
||||
{
|
||||
case 1: // stream more adx data
|
||||
// adx->buffer has two buffer chunks (ADX_FILE_BUFFER_SIZE*2)
|
||||
// read the data into the 2nd chunk
|
||||
n = fread((void *)(adx->buffer + ADX_FILE_BUFFER_SIZE), 1, ADX_FILE_BUFFER_SIZE, adx_in);
|
||||
|
||||
if (adx->ADX_Info.loop && ftell( adx_in ) >= adx->ADX_Info.loop_end)
|
||||
{
|
||||
// handle sample-based looping
|
||||
fseek(adx_in, -n, SEEK_CUR);
|
||||
n = adx->ADX_Info.loop_end - ftell(adx_in);
|
||||
fread((void *)(adx->buffer + ADX_FILE_BUFFER_SIZE), 1, n, adx_in);
|
||||
|
||||
// seek to specific sample position
|
||||
fseek(adx_in, adx->ADX_Info.loop_start, SEEK_SET);
|
||||
adx->ADX_Info.samples = adx->ADX_Info.loop_samples;
|
||||
|
||||
fread((void *)(adx->buffer + ADX_FILE_BUFFER_SIZE + n), 1, ADX_FILE_BUFFER_SIZE-n, adx_in);
|
||||
}
|
||||
else if (n < ADX_FILE_BUFFER_SIZE)
|
||||
{
|
||||
if (adx->loop)
|
||||
{
|
||||
if (adx->ADX_Info.loop)
|
||||
{
|
||||
// start at specific sample position
|
||||
fseek(adx_in, adx->ADX_Info.loop_start, SEEK_SET);
|
||||
adx->ADX_Info.samples = adx->ADX_Info.loop_samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
// start at beginning
|
||||
fseek(adx_in, adx->ADX_Info.sample_offset+ADX_CRI_SIZE, SEEK_SET);
|
||||
}
|
||||
|
||||
n = fread((void *)(adx->buffer + ADX_FILE_BUFFER_SIZE + n), 1, ADX_FILE_BUFFER_SIZE-n, adx_in);
|
||||
}
|
||||
}
|
||||
adx->flag = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int adx_play(const char* adx_file, int loop_enable)
|
||||
{
|
||||
if (!adx || adx_in) return 0;
|
||||
|
||||
memset((void*)adx, 0, sizeof(adx_player));
|
||||
|
||||
adx_in = fopen(adx_file, "rb");
|
||||
if (!adx_in)
|
||||
return 0;
|
||||
|
||||
unsigned char adx_buf[ADX_HDR_SIZE];
|
||||
if (!adx_parse(adx_buf))
|
||||
{
|
||||
fclose(adx_in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
adx_msg msg;
|
||||
|
||||
adx->buffer = adx_buffer;
|
||||
adx->audioLeft = adx_audioLeft;
|
||||
adx->audioRight = adx_audioRight;
|
||||
adx->loop = loop_enable;
|
||||
|
||||
msg.type = ADX_MSG_START;
|
||||
msg.player = adx;
|
||||
|
||||
fread((void *)(adx_buffer), 1, ADX_FILE_BUFFER_SIZE*2, adx_in);
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
|
||||
int timeout = 60*3;
|
||||
while (!fifoCheckValue32(FIFO_USER_01) && --timeout)
|
||||
swiWaitForVBlank();
|
||||
|
||||
int ret = (!timeout) ? -1 : (int)fifoGetValue32(FIFO_USER_01);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int adx_pause()
|
||||
{
|
||||
if (!adx || !adx_in) return 0;
|
||||
|
||||
adx_msg msg;
|
||||
msg.type = ADX_MSG_PAUSE;
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int adx_resume()
|
||||
{
|
||||
if (!adx || !adx_in) return 0;
|
||||
|
||||
adx_msg msg;
|
||||
msg.type = ADX_MSG_RESUME;
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int adx_set_volume(int volume)
|
||||
{
|
||||
if (!adx || !adx_in) return 0;
|
||||
|
||||
adx_msg msg;
|
||||
|
||||
msg.type = ADX_MSG_VOLUME;
|
||||
msg.volume = volume;
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
while(!fifoCheckValue32(FIFO_USER_01));
|
||||
|
||||
return (int)fifoGetValue32(FIFO_USER_01);
|
||||
}
|
||||
|
||||
int adx_restart()
|
||||
{
|
||||
if (!adx || !adx_in) return 0;
|
||||
|
||||
adx_msg msg;
|
||||
msg.type = ADX_MSG_RESTART;
|
||||
|
||||
fseek( adx_in, adx->ADX_Info.sample_offset+ADX_CRI_SIZE, SEEK_SET );
|
||||
fread((void *)(adx_buffer), 1, ADX_FILE_BUFFER_SIZE*2, adx_in);
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int adx_stop()
|
||||
{
|
||||
if (!adx || !adx_in) return 1;
|
||||
|
||||
adx_msg msg;
|
||||
|
||||
msg.type = ADX_MSG_STOP;
|
||||
|
||||
fifoSendDatamsg(FIFO_USER_01, sizeof(msg), (u8*)&msg);
|
||||
while(!fifoCheckValue32(FIFO_USER_01));
|
||||
|
||||
int ret = (int)fifoGetValue32(FIFO_USER_01);
|
||||
|
||||
fclose(adx_in);
|
||||
adx_in = 0;
|
||||
|
||||
adx->flag = 0;
|
||||
|
||||
return ret;
|
||||
}
|
98
arm9/source/test.cpp
Normal file
98
arm9/source/test.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
// libadx-nds test program
|
||||
// by headshot2017 (Headshotnoby)
|
||||
// LibADX Dreamcast library (c)2012 Josh PH3NOM Pearson
|
||||
// Built on top of NDS Helix-MP3 decoder code by sverx (https://adshomebrewersdiary.blogspot.com/2012/06/mp3-streaming-on-arm7.html)
|
||||
|
||||
#include <nds.h>
|
||||
#include <stdio.h>
|
||||
#include <filesystem.h>
|
||||
|
||||
#include "libadx.h"
|
||||
|
||||
static void waitAnyKey()
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
|
||||
if (keysDown())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
videoSetModeSub(MODE_0_2D);
|
||||
vramSetBankC(VRAM_C_SUB_BG);
|
||||
consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, 0, 1, false, true);
|
||||
|
||||
printf("libadx-nds test program\n\n");
|
||||
|
||||
printf("Loading NitroFS...");
|
||||
if (!nitroFSInit(0))
|
||||
{
|
||||
printf("Failed!\nPress any button to exit...\n");
|
||||
waitAnyKey();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Success!\nInit ADX library...");
|
||||
if (!adx_init())
|
||||
{
|
||||
printf("Failed!\nPress any button to exit...\n");
|
||||
waitAnyKey();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Success!\nPlay ADX file...");
|
||||
int err = adx_play("nitro:/test.adx", 1);
|
||||
if(err <= 0)
|
||||
{
|
||||
printf("Failed! %d\nPress any button to exit...\n", err);
|
||||
waitAnyKey();
|
||||
return 1;
|
||||
}
|
||||
printf("Success!\n\n");
|
||||
|
||||
printf("A: Play\n");
|
||||
printf("B: Stop\n");
|
||||
printf("Y: Pause/Resume\n");
|
||||
printf("X: Restart\n");
|
||||
printf("DPad: Volume up/down\n");
|
||||
printf("Start: Exit\n");
|
||||
|
||||
int vol = 127;
|
||||
bool paused = false;
|
||||
|
||||
while(1) {
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
|
||||
int down = keysDown();
|
||||
int held = keysHeld();
|
||||
|
||||
if (!(held & KEY_SELECT)) adx_update();
|
||||
if (held & KEY_UP && vol < 127)
|
||||
{
|
||||
vol++;
|
||||
adx_set_volume(vol);
|
||||
}
|
||||
if (held & KEY_DOWN && vol > 0)
|
||||
{
|
||||
vol--;
|
||||
adx_set_volume(vol);
|
||||
}
|
||||
|
||||
if (down & KEY_A) adx_play("nitro:/test.adx", 1);
|
||||
if (down & KEY_B) adx_stop();
|
||||
if (down & KEY_X) adx_restart();
|
||||
if (down & KEY_Y)
|
||||
{
|
||||
(paused) ? adx_resume() : adx_pause();
|
||||
paused ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
132
common/libadx.h
Normal file
132
common/libadx.h
Normal file
@ -0,0 +1,132 @@
|
||||
#ifndef LIBADX_H
|
||||
#define LIBADX_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ADX_CRI_SIZE 0x06
|
||||
#define ADX_PAD_SIZE 0x0e
|
||||
#define ADX_HDR_SIZE 0x2c
|
||||
#define ADX_HDR_SIG 0x80
|
||||
#define ADX_EXIT_SIG 0x8001
|
||||
|
||||
#define ADX_ADDR_START 0x02
|
||||
#define ADX_ADDR_CHUNK 0x05
|
||||
#define ADX_ADDR_CHAN 0x07
|
||||
#define ADX_ADDR_RATE 0x08
|
||||
#define ADX_ADDR_SAMP 0x0c
|
||||
#define ADX_ADDR_TYPE 0x12
|
||||
#define ADX_ADDR_LOOP 0x18
|
||||
#define ADX_ADDR_SAMP_START 0x1c
|
||||
#define ADX_ADDR_BYTE_START 0x20
|
||||
#define ADX_ADDR_SAMP_END 0x24
|
||||
#define ADX_ADDR_BYTE_END 0x28
|
||||
|
||||
#define BASEVOL 0x4000
|
||||
|
||||
#define ADX_FILE_BUFFER_SIZE (8 * 1024)
|
||||
#define ADX_AUDIO_BUFFER_SAMPS (8 * 1024)
|
||||
#define ADX_AUDIO_BUFFER_SIZE (ADX_AUDIO_BUFFER_SAMPS * 2)
|
||||
|
||||
typedef vu32 DSTIME;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vs32 sample_offset;
|
||||
vs32 chunk_size;
|
||||
vs32 channels;
|
||||
vs32 rate;
|
||||
vs32 samples;
|
||||
vs32 loop_type;
|
||||
vs32 loop;
|
||||
vs32 loop_start;
|
||||
vs32 loop_end;
|
||||
vs32 loop_samp_start;
|
||||
vs32 loop_samp_end;
|
||||
vs32 loop_samples;
|
||||
}ADX_INFO;
|
||||
|
||||
typedef struct {
|
||||
vs32 s1,s2;
|
||||
} PREV;
|
||||
|
||||
typedef enum {
|
||||
ADX_IDLE,
|
||||
ADX_STARTING,
|
||||
ADX_PLAYING,
|
||||
ADX_PAUSING,
|
||||
ADX_RESUMING,
|
||||
ADX_PAUSED,
|
||||
ADX_STOPPING,
|
||||
ADX_RESTARTING,
|
||||
ADX_ERROR=0xffffffff
|
||||
} adx_player_state;
|
||||
|
||||
typedef struct {
|
||||
ADX_INFO ADX_Info;
|
||||
vs32 loop;
|
||||
PREV prev[2];
|
||||
|
||||
u8* buffer;
|
||||
u16* audioLeft;
|
||||
u16* audioRight;
|
||||
vu32 flag;
|
||||
} adx_player;
|
||||
|
||||
typedef struct {
|
||||
u32 type;
|
||||
union
|
||||
{
|
||||
volatile adx_player *player;
|
||||
u32 volume;
|
||||
};
|
||||
} adx_msg;
|
||||
|
||||
enum {
|
||||
ADX_MSG_START,
|
||||
ADX_MSG_STOP,
|
||||
ADX_MSG_PAUSE,
|
||||
ADX_MSG_RESUME,
|
||||
ADX_MSG_VOLUME,
|
||||
ADX_MSG_RESTART,
|
||||
ADX_MSG_ERROR=0xffffffff,
|
||||
};
|
||||
|
||||
|
||||
// LibADX Public Function Definitions
|
||||
// Return 1 on success, 0 on failure
|
||||
|
||||
// Initialize the library
|
||||
int adx_init();
|
||||
|
||||
// Call this every frame
|
||||
void adx_update();
|
||||
|
||||
#ifdef ARM9
|
||||
|
||||
// Start streaming the ADX in ARM7
|
||||
int adx_play(const char * adx_file, int loop_enable);
|
||||
|
||||
// Pause ADX stream
|
||||
int adx_pause();
|
||||
|
||||
// Resume a paused ADX stream
|
||||
int adx_resume();
|
||||
|
||||
// Set the ADX volume from range 0-127
|
||||
int adx_set_volume(int volume);
|
||||
|
||||
// Restart the ADX from the beginning
|
||||
int adx_restart();
|
||||
|
||||
// Stop ADX stream
|
||||
int adx_stop();
|
||||
|
||||
#endif // ARM9
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // LIBADX_H
|
1
nitro/song.txt
Normal file
1
nitro/song.txt
Normal file
@ -0,0 +1 @@
|
||||
"Back To Mad" by Texas Faggott
|
BIN
nitro/test.adx
Normal file
BIN
nitro/test.adx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user