first commit

This commit is contained in:
headshot2017 2024-12-08 21:49:47 -04:00
commit 77cebdcc56
13 changed files with 1119 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
*.elf
*.nds

26
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
"Back To Mad" by Texas Faggott

BIN
nitro/test.adx Normal file

Binary file not shown.