summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMagnus Holmgren <magnushol@gmail.com>2005-07-24 15:32:28 +0000
committerMagnus Holmgren <magnushol@gmail.com>2005-07-24 15:32:28 +0000
commit4a53787992b396d28c001f7567bc91644fae861c (patch)
tree0ab4fee5b878e957fa781d6581efbe6f103c0fd2
parent6bd8e5db08e42130d1a72377b3c0cec0b8d57a69 (diff)
downloadrockbox-4a53787992b396d28c001f7567bc91644fae861c.zip
rockbox-4a53787992b396d28c001f7567bc91644fae861c.tar.gz
rockbox-4a53787992b396d28c001f7567bc91644fae861c.tar.bz2
rockbox-4a53787992b396d28c001f7567bc91644fae861c.tar.xz
ReplayGain support for Ogg Vorbis files (also called VorbisGain) added.
Note that there is a small delay from leaving a setting until the change can be heard (due to audio data buffering). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7234 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/codecs/Tremor/ivorbisfile.h2
-rw-r--r--apps/codecs/Tremor/vorbisfile.c44
-rw-r--r--apps/codecs/lib/codeclib.c9
-rw-r--r--apps/codecs/lib/codeclib.h2
-rw-r--r--apps/codecs/vorbis.c32
-rw-r--r--apps/dsp.c138
-rw-r--r--apps/dsp.h1
-rw-r--r--apps/lang/english.lang53
-rw-r--r--apps/metadata.c42
-rw-r--r--apps/playback.h6
-rw-r--r--apps/screens.c19
-rw-r--r--apps/settings.c3
-rw-r--r--apps/settings.h6
-rw-r--r--apps/settings_menu.c52
-rw-r--r--firmware/SOURCES3
-rw-r--r--firmware/app.lds5
-rw-r--r--firmware/export/id3.h25
17 files changed, 404 insertions, 38 deletions
diff --git a/apps/codecs/Tremor/ivorbisfile.h b/apps/codecs/Tremor/ivorbisfile.h
index 1ec0f74..9ff446a 100644
--- a/apps/codecs/Tremor/ivorbisfile.h
+++ b/apps/codecs/Tremor/ivorbisfile.h
@@ -121,6 +121,8 @@ extern vorbis_comment *ov_comment(OggVorbis_File *vf,int link);
extern long ov_read(OggVorbis_File *vf,char *buffer,int length,
int *bitstream);
+extern long ov_read_fixed(OggVorbis_File *vf,ogg_int32_t ***pcm_channels,
+ int length,int *bitstream);
#ifdef __cplusplus
}
diff --git a/apps/codecs/Tremor/vorbisfile.c b/apps/codecs/Tremor/vorbisfile.c
index f6a208d..0d8c04a 100644
--- a/apps/codecs/Tremor/vorbisfile.c
+++ b/apps/codecs/Tremor/vorbisfile.c
@@ -1604,3 +1604,47 @@ long ov_read(OggVorbis_File *vf,char *buffer,int bytes_req,int *bitstream){
return(samples);
}
}
+
+/* input values: pcm_channels) a float vector per channel of output
+ length) the sample length being read by the app
+
+ return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL)
+ 0) EOF
+ n) number of samples of PCM actually returned. The
+ below works on a packet-by-packet basis, so the
+ return length is not related to the 'length' passed
+ in, just guaranteed to fit.
+
+ *section) set to the logical bitstream number */
+
+long ov_read_fixed(OggVorbis_File *vf,ogg_int32_t ***pcm_channels,int length,
+ int *bitstream){
+ if(vf->ready_state<OPENED)return(OV_EINVAL);
+
+#if CONFIG_CPU == MCF5249
+ mcf5249_init_mac();
+#endif
+
+ while(1){
+ if(vf->ready_state==INITSET){
+ ogg_int32_t **pcm;
+ long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
+ if(samples){
+ if(pcm_channels)*pcm_channels=pcm;
+ if(samples>length)samples=length;
+ vorbis_synthesis_read(&vf->vd,samples);
+ vf->pcm_offset+=samples;
+ if(bitstream)*bitstream=vf->current_link;
+ return samples;
+
+ }
+ }
+
+ /* suck in another packet */
+ {
+ int ret=_fetch_and_process_packet(vf,1,1);
+ if(ret==OV_EOF)return(0);
+ if(ret<=0)return(ret);
+ }
+ }
+}
diff --git a/apps/codecs/lib/codeclib.c b/apps/codecs/lib/codeclib.c
index 2494fe5..ac6e362 100644
--- a/apps/codecs/lib/codeclib.c
+++ b/apps/codecs/lib/codeclib.c
@@ -23,6 +23,7 @@
#include "playback.h"
#include "codeclib.h"
#include "xxx2wav.h"
+#include "id3.h"
struct codec_api *local_rb;
@@ -34,3 +35,11 @@ int codec_init(struct codec_api* rb)
return 0;
}
+
+void codec_set_replaygain(struct mp3entry* id3)
+{
+ local_rb->configure(DSP_SET_TRACK_GAIN, (long *) id3->track_gain);
+ local_rb->configure(DSP_SET_ALBUM_GAIN, (long *) id3->album_gain);
+ local_rb->configure(DSP_SET_TRACK_PEAK, (long *) id3->track_peak);
+ local_rb->configure(DSP_SET_ALBUM_PEAK, (long *) id3->album_peak);
+}
diff --git a/apps/codecs/lib/codeclib.h b/apps/codecs/lib/codeclib.h
index 77276fb..3fc03bd 100644
--- a/apps/codecs/lib/codeclib.h
+++ b/apps/codecs/lib/codeclib.h
@@ -37,4 +37,4 @@ int memcmp(const void *s1, const void *s2, size_t n);
void* memmove(const void *s1, const void *s2, size_t n);
int codec_init(struct codec_api* rb);
-
+void codec_set_replaygain(struct mp3entry* id3);
diff --git a/apps/codecs/vorbis.c b/apps/codecs/vorbis.c
index 8aa7f21..2976a05 100644
--- a/apps/codecs/vorbis.c
+++ b/apps/codecs/vorbis.c
@@ -105,16 +105,11 @@ bool vorbis_set_codec_parameters(OggVorbis_File *vf)
return false;
}
- if (rb->id3->frequency != NATIVE_FREQUENCY) {
- rb->configure(CODEC_DSP_ENABLE, (bool *)true);
- } else {
- rb->configure(CODEC_DSP_ENABLE, (bool *)false);
- }
-
rb->configure(DSP_SET_FREQUENCY, (int *)rb->id3->frequency);
+ codec_set_replaygain(rb->id3);
if (vi->channels == 2) {
- rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_INTERLEAVED);
+ rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_NONINTERLEAVED);
} else if (vi->channels == 1) {
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
}
@@ -129,14 +124,12 @@ extern char iramend[];
#endif
-/* reserve the PCM buffer in the IRAM area */
-static char pcmbuf[4096] IDATA_ATTR;
-
/* this is the codec entry point */
enum codec_status codec_start(struct codec_api* api)
{
ov_callbacks callbacks;
OggVorbis_File vf;
+ ogg_int32_t** pcm;
int error;
long n;
@@ -157,10 +150,12 @@ enum codec_status codec_start(struct codec_api* api)
#ifdef USE_IRAM
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
#endif
-
- rb->configure(DSP_DITHER, (bool *)false);
- rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
+ rb->configure(CODEC_DSP_ENABLE, (bool *)true);
+ rb->configure(DSP_DITHER, (bool *)false);
+ rb->configure(DSP_SET_SAMPLE_DEPTH, (long *) (24));
+ rb->configure(DSP_SET_CLIP_MAX, (long *) ((1 << 24) - 1));
+ rb->configure(DSP_SET_CLIP_MIN, (long *) -((1 << 24) - 1));
/* Note: These are sane defaults for these values. Perhaps
* they should be set differently based on quality setting
*/
@@ -244,9 +239,9 @@ enum codec_status codec_start(struct codec_api* api)
}
rb->seek_time = 0;
}
-
- /* Read host-endian signed 16 bit PCM samples */
- n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),&current_section);
+
+ /* Read host-endian signed 24-bit PCM samples */
+ n=ov_read_fixed(&vf,&pcm,1024,&current_section);
/* Change DSP and buffer settings for this bitstream */
if ( current_section != previous_section ) {
@@ -262,9 +257,10 @@ enum codec_status codec_start(struct codec_api* api)
} else if (n < 0) {
DEBUGF("Error decoding frame\n");
} else {
- while (!rb->pcmbuf_insert(pcmbuf, n)) {
+ while (!rb->pcmbuf_insert_split(pcm[0], pcm[1],
+ n * sizeof(ogg_int32_t))) {
rb->sleep(1);
- }
+ }
rb->set_offset(ov_raw_tell(&vf));
rb->set_elapsed(ov_time_tell(&vf));
diff --git a/apps/dsp.c b/apps/dsp.c
index cd22610..ea0dece 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -16,11 +16,14 @@
* KIND, either express or implied.
*
****************************************************************************/
+#include <inttypes.h>
#include <string.h>
#include "dsp.h"
#include "kernel.h"
#include "playback.h"
#include "system.h"
+#include "settings.h"
+#include "debug.h"
/* The "dither" code to convert the 24-bit samples produced by libmad was
* taken from the coolplayer project - coolplayer.sourceforge.net
@@ -35,6 +38,7 @@
#define NATIVE_DEPTH 16
#define SAMPLE_BUF_SIZE 256
#define RESAMPLE_BUF_SIZE (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
+#define DEFAULT_REPLAYGAIN 0x01000000
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
@@ -45,16 +49,31 @@
#define FRACMUL(x, y) \
({ \
long t; \
- asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
+ asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
"movclr.l %%acc0, %[t]\n\t" \
: [t] "=r" (t) : [a] "r" (x), [b] "r" (y)); \
t; \
})
+/* Multiply 2 32-bit integers and of the 40 most significat bits of the
+ * result, return the 32 least significant bits. I.e., like FRACMUL with one
+ * of the arguments shifted 8 bits to the right.
+ */
+#define FRACMUL_8(x, y) \
+({ \
+ long t; \
+ long u; \
+ asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
+ "move.l %%accext01, %[u]\n\t" \
+ "movclr.l %%acc0, %[t]\n\t" \
+ : [t] "=r" (t), [u] "=r" (u) : [a] "r" (x), [b] "r" (y)); \
+ (t << 8) | (u & 0xff); \
+})
#else
#define INIT()
#define FRACMUL(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 32))
+#define FRACMUL_8(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 24))
#endif
@@ -63,11 +82,17 @@ struct dsp_config
long frequency;
long clip_min;
long clip_max;
+ long track_gain;
+ long album_gain;
+ long track_peak;
+ long album_peak;
+ long replaygain;
int sample_depth;
int sample_bytes;
int stereo_mode;
int frac_bits;
bool dither_enabled;
+ bool new_gain;
};
struct resample_data
@@ -197,8 +222,6 @@ static long downsample(long *dst, long *src, int count,
int pos = phase >> 16;
int i = 1;
- INIT();
-
/* Do we need last sample of previous frame for interpolation? */
if (pos > 0)
{
@@ -232,8 +255,6 @@ static long upsample(long *dst, long *src, int count, struct resample_data *r)
int i = 0;
int pos;
- INIT();
-
while ((pos = phase >> 16) == 0)
{
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
@@ -352,6 +373,40 @@ static long dither_sample(long sample, long bias, long mask,
return output;
}
+/* Apply a constant gain to the samples (e.g., for ReplayGain). May update
+ * the src array if gain was applied.
+ * Note that this must be called before the resampler.
+ */
+static void apply_gain(long* src[], int count)
+{
+ if (dsp.replaygain)
+ {
+ long* s0 = src[0];
+ long* s1 = src[1];
+ long* d0 = &sample_buf[0];
+ long* d1 = (s0 == s1) ? d0 : &sample_buf[SAMPLE_BUF_SIZE / 2];
+ long gain = dsp.replaygain;
+ long i;
+
+
+ src[0] = d0;
+ src[1] = d1;
+
+ for (i = 0; i < count; i++)
+ {
+ *d0++ = FRACMUL_8(*s0++, gain);
+ }
+
+ if (s0 != s1)
+ {
+ for (i = 0; i < count; i++)
+ {
+ *d1++ = FRACMUL_8(*s1++, gain);
+ }
+ }
+ }
+}
+
static void write_samples(short* dst, long* src[], int count)
{
long* s0 = src[0];
@@ -397,11 +452,14 @@ long dsp_process(char* dst, char* src[], long size)
int samples;
size /= dsp.sample_bytes * factor;
+ INIT();
+ dsp_set_replaygain(false);
while (size > 0)
{
samples = convert_to_internal(src, size, tmp);
size -= samples;
+ apply_gain(tmp, samples);
samples = resample(tmp, samples);
write_samples((short*) dst, tmp, samples);
written += samples;
@@ -514,9 +572,14 @@ bool dsp_configure(int setting, void *value)
dsp.stereo_mode = STEREO_NONINTERLEAVED;
dsp.clip_max = ((1 << WORD_FRACBITS) - 1);
dsp.clip_min = -((1 << WORD_FRACBITS));
+ dsp.track_gain = 0;
+ dsp.album_gain = 0;
+ dsp.track_peak = 0;
+ dsp.album_peak = 0;
dsp.frequency = NATIVE_FREQUENCY;
dsp.sample_depth = NATIVE_DEPTH;
dsp.frac_bits = WORD_FRACBITS;
+ dsp.new_gain = true;
break;
case DSP_DITHER:
@@ -524,9 +587,74 @@ bool dsp_configure(int setting, void *value)
dsp.dither_enabled = (bool) value;
break;
+ case DSP_SET_TRACK_GAIN:
+ dsp.track_gain = (long) value;
+ dsp.new_gain = true;
+ break;
+
+ case DSP_SET_ALBUM_GAIN:
+ dsp.album_gain = (long) value;
+ dsp.new_gain = true;
+ break;
+
+ case DSP_SET_TRACK_PEAK:
+ dsp.track_peak = (long) value;
+ dsp.new_gain = true;
+ break;
+
+ case DSP_SET_ALBUM_PEAK:
+ dsp.album_peak = (long) value;
+ dsp.new_gain = true;
+ break;
+
default:
return 0;
}
return 1;
}
+
+void dsp_set_replaygain(bool always)
+{
+ if (always || dsp.new_gain)
+ {
+ long gain = 0;
+
+ dsp.new_gain = false;
+
+ if (global_settings.replaygain || global_settings.replaygain_noclip)
+ {
+ long peak;
+
+ if (global_settings.replaygain)
+ {
+ gain = (global_settings.replaygain_track || !dsp.album_gain)
+ ? dsp.track_gain : dsp.album_gain;
+ }
+
+ peak = (global_settings.replaygain_track || !dsp.album_peak)
+ ? dsp.track_peak : dsp.album_peak;
+
+ if (gain == 0)
+ {
+ /* So that noclip can work even with no gain information. */
+ gain = DEFAULT_REPLAYGAIN;
+ }
+
+ if (global_settings.replaygain_noclip && (peak != 0)
+ && ((((int64_t) gain * peak) >> 24) >= DEFAULT_REPLAYGAIN))
+ {
+ gain = (((int64_t) DEFAULT_REPLAYGAIN << 24) / peak);
+ }
+
+ if (gain == DEFAULT_REPLAYGAIN)
+ {
+ /* Nothing to do, disable processing. */
+ gain = 0;
+
+ }
+ }
+
+ dsp.replaygain = gain;
+ }
+}
diff --git a/apps/dsp.h b/apps/dsp.h
index 723280b..856c08f 100644
--- a/apps/dsp.h
+++ b/apps/dsp.h
@@ -33,5 +33,6 @@ long dsp_input_size(long size);
long dsp_output_size(long size);
int dsp_stereo_mode(void);
bool dsp_configure(int setting, void *value);
+void dsp_set_replaygain(bool always);
#endif
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index f2f38d2..f8c0c33 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -3185,3 +3185,56 @@ eng: "Restarting playback..."
voice: "Restarting playback..."
new:
+id: LANG_REPLAYGAIN
+desc: in replaygain
+eng: "Replaygain"
+voice "Replaygain"
+new:
+
+id: LANG_REPLAYGAIN_ENABLE
+desc: in replaygain
+eng: "Enable replaygain"
+voice "Enable replaygain"
+new:
+
+id: LANG_REPLAYGAIN_NOCLIP
+desc: in replaygain
+eng: "Prevent clipping"
+voice "Prevent clipping"
+new:
+
+id: LANG_REPLAYGAIN_MODE
+desc: in replaygain
+eng: "Replaygain type"
+voice "Replaygain type"
+new:
+
+id: LANG_TRACK_GAIN
+desc: in replaygain
+eng: "Track gain"
+voice "Track gain"
+new:
+
+id: LANG_ALBUM_GAIN
+desc: in replaygain
+eng: "Album gain"
+voice "Album gain"
+new:
+
+id: LANG_ID3_TRACK_GAIN
+desc: in browse_id3
+eng: "[Track gain]"
+voice ""
+new:
+
+id: LANG_ID3_ALBUM_GAIN
+desc: in browse_id3
+eng: "[Album gain]"
+voice ""
+new:
+
+id: LANG_ID3_NO_GAIN
+desc: in browse_id3
+eng: "<No gain>"
+voice ""
+new:
diff --git a/apps/metadata.c b/apps/metadata.c
index 48bf637..09bcb55 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -25,6 +25,8 @@
#include "mp3_playback.h"
#include "logf.h"
#include "atoi.h"
+#include "replaygain.h"
+#include "debug.h"
/* Simple file type probing by looking filename extension. */
int probe_file_format(const char *filename)
@@ -271,7 +273,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
channels=buf[39];
if ( !get_vorbis_comments(&(track->id3), fd) ) {
- logf("get_vorbis_comments failed");
+ logf("get_vorbis_comments failed");
return(false);
}
@@ -283,7 +285,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
/* We now need to search for the last page in the file - identified by
by ('O','g','g','S',0) and retrieve totalsamples */
- lseek(fd, -32*1024, SEEK_END);
+ lseek(fd, -64*1024, SEEK_END); /* A page is always < 64 kB */
eof=0;
j=0; /* The number of bytes currently in buffer */
i=0;
@@ -300,6 +302,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
while (i < (j-5)) {
if (memcmp(&buf[i],"OggS",5)==0) {
if (i < (j-17)) {
+ /* Note that this only reads the low 32 bits of a 64 bit value */
totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24);
last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24);
j=0; /* We can discard the rest of the buffer */
@@ -761,7 +764,7 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
int comment_length;
int i = 0;
unsigned char temp[300];
- int buffer_remaining = sizeof(entry->id3v2buf);
+ int buffer_remaining = sizeof(entry->id3v2buf) + sizeof(entry->id3v1buf);
char *buffer = entry->id3v2buf;
char **p = NULL;
int segments;
@@ -884,8 +887,36 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
name_length = 11;
p = &(entry->track_string);
+ } else if ((strncasecmp(temp, "RG_RADIO=", 9) == 0)
+ && !entry->track_gain) {
+ entry->track_gain = get_replaygain(&temp[9]);
+ name_length = 8;
+ p = &(entry->track_gain_str);
+ } else if (strncasecmp(temp, "REPLAYGAIN_TRACK_GAIN=", 22) == 0) {
+ entry->track_gain = get_replaygain(&temp[22]);
+ name_length = 21;
+ p = &(entry->track_gain_str);
+ } else if ((strncasecmp(temp, "RG_AUDIOPHILE=", 14) == 0)
+ && !entry->album_gain) {
+ entry->album_gain = get_replaygain(&temp[14]);
+ name_length = 13;
+ p = &(entry->album_gain_str);
+ } else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_GAIN=", 22) == 0) {
+ entry->album_gain = get_replaygain(&temp[22]);
+ name_length = 21;
+ p = &(entry->album_gain_str);
+ } else if ((strncasecmp(temp, "RG_PEAK=", 8) == 0)
+ && !entry->track_peak) {
+ entry->track_peak = get_replaypeak(&temp[8]);
+ p = NULL;
+ } else if (strncasecmp(temp, "REPLAYGAIN_TRACK_PEAK=", 22) == 0) {
+ entry->track_peak = get_replaypeak(&temp[22]);
+ p = NULL;
+ } else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_PEAK=", 22) == 0) {
+ entry->album_peak = get_replaypeak(&temp[22]);
+ p = NULL;
} else {
- p = NULL;
+ p = NULL;
}
if (p) {
@@ -899,7 +930,6 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
}
}
}
-
+
return true;
}
-
diff --git a/apps/playback.h b/apps/playback.h
index cb006f9..5b69228 100644
--- a/apps/playback.h
+++ b/apps/playback.h
@@ -38,7 +38,11 @@ enum {
DSP_SET_SAMPLE_DEPTH,
DSP_SET_STEREO_MODE,
DSP_RESET,
- DSP_DITHER
+ DSP_DITHER,
+ DSP_SET_TRACK_GAIN,
+ DSP_SET_ALBUM_GAIN,
+ DSP_SET_TRACK_PEAK,
+ DSP_SET_ALBUM_PEAK
};
/* Not yet implemented. */
diff --git a/apps/screens.c b/apps/screens.c
index ab3ae0f..440f9e1 100644
--- a/apps/screens.c
+++ b/apps/screens.c
@@ -1274,7 +1274,11 @@ bool browse_id3(void)
struct mp3entry* id3 = audio_current_track();
int button;
int menu_pos = 0;
+#if CONFIG_HWCODEC == MASNONE
+ int menu_max = 12;
+#else
int menu_max = 10;
+#endif
bool exit = false;
char scroll_text[MAX_PATH];
@@ -1381,6 +1385,21 @@ bool browse_id3(void)
lcd_puts(0, 0, str(LANG_ID3_PATH));
lcd_puts_scroll(0, 1, id3->path);
break;
+#if CONFIG_HWCODEC == MASNONE
+ case 11:
+ lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN));
+ lcd_puts(0, 1, id3->track_gain_str
+ ? id3->track_gain_str
+ : (char*) str(LANG_ID3_NO_GAIN));
+ break;
+
+ case 12:
+ lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN));
+ lcd_puts(0, 1, id3->album_gain_str
+ ? id3->album_gain_str
+ : (char*) str(LANG_ID3_NO_GAIN));
+ break;
+#endif
}
lcd_update();
diff --git a/apps/settings.c b/apps/settings.c
index efb9cfd..5053c86 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -416,6 +416,9 @@ static const struct bit_entry hd_bits[] =
#if CONFIG_HWCODEC == MASNONE
{2, S_O(crossfade), 0, "crossfade type", "off,crossfade,mix"},
+ {1, S_O(replaygain), false, "replaygain", off_on },
+ {1, S_O(replaygain_track), false, "replaygain type", "track,album" },
+ {1, S_O(replaygain_noclip), false, "replaygain noclip", off_on },
#endif
/* new stuff to be added at the end */
diff --git a/apps/settings.h b/apps/settings.h
index 3e3a982..e0a61bf 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -328,6 +328,12 @@ struct user_settings
bool next_folder; /* move to next folder */
bool runtimedb; /* runtime database active? */
+
+#if CONFIG_HWCODEC == MASNONE
+ bool replaygain; /* enable replaygain */
+ bool replaygain_track; /* true for track gain, false for album gain */
+ bool replaygain_noclip; /* scale to prevent clips */
+#endif
};
enum optiontype { INT, BOOL };
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index 5b03a8d..50f1396 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -66,6 +66,7 @@ void dac_line_in(bool enable);
#if CONFIG_HWCODEC == MASNONE
#include "pcmbuf.h"
#include "pcm_playback.h"
+#include "dsp.h"
#endif
#ifdef HAVE_CHARGING
@@ -1187,6 +1188,56 @@ static bool runtimedb(void)
return rc;
}
+#if CONFIG_HWCODEC == MASNONE
+static bool replaygain(void)
+{
+ bool result = set_bool(str(LANG_REPLAYGAIN_ENABLE),
+ &global_settings.replaygain);
+
+ dsp_set_replaygain(true);
+ return result;
+}
+
+static bool replaygain_mode(void)
+{
+ bool result = set_bool_options(str(LANG_REPLAYGAIN_MODE),
+ &global_settings.replaygain_track,
+ STR(LANG_TRACK_GAIN),
+ STR(LANG_ALBUM_GAIN),
+ NULL);
+
+ dsp_set_replaygain(true);
+ return result;
+}
+
+static bool replaygain_noclip(void)
+{
+ bool result = set_bool(str(LANG_REPLAYGAIN_NOCLIP),
+ &global_settings.replaygain_noclip);
+
+ dsp_set_replaygain(true);
+ return result;
+}
+
+static bool replaygain_settings_menu(void)
+{
+ int m;
+ bool result;
+
+ static const struct menu_item items[] = {
+ { ID2P(LANG_REPLAYGAIN_ENABLE), replaygain },
+ { ID2P(LANG_REPLAYGAIN_NOCLIP), replaygain_noclip },
+ { ID2P(LANG_REPLAYGAIN_MODE), replaygain_mode },
+ };
+
+ m=menu_init( items, sizeof(items) / sizeof(*items), NULL,
+ NULL, NULL, NULL);
+ result = menu_run(m);
+ menu_exit(m);
+ return result;
+}
+#endif
+
static bool playback_settings_menu(void)
{
int m;
@@ -1203,6 +1254,7 @@ static bool playback_settings_menu(void)
#if CONFIG_HWCODEC == MASNONE
{ ID2P(LANG_CROSSFADE), crossfade },
{ ID2P(LANG_CROSSFADE_DURATION), crossfade_duration },
+ { ID2P(LANG_REPLAYGAIN), replaygain_settings_menu },
#endif
#ifdef HAVE_SPDIF_POWER
{ ID2P(LANG_SPDIF_ENABLE), spdif },
diff --git a/firmware/SOURCES b/firmware/SOURCES
index afb36f4..5fbe6be 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -129,6 +129,9 @@ drivers/uda1380.c
#if (CONFIG_HWCODEC == MASNONE) && !defined(SIMULATOR)
pcm_playback.c
#endif
+#if CONFIG_HWCODEC == MASNONE
+replaygain.c
+#endif
#if defined(HAVE_UDA1380) && !defined(SIMULATOR)
pcm_record.c
#endif
diff --git a/firmware/app.lds b/firmware/app.lds
index ef7cb19..0c9fa8d 100644
--- a/firmware/app.lds
+++ b/firmware/app.lds
@@ -179,6 +179,11 @@ SECTIONS
_iramcopy = .;
} > DRAM
+ /DISCARD/ :
+ {
+ *(.eh_frame)
+ }
+
.iram IRAMORIG : AT ( _iramcopy)
{
_iramstart = .;
diff --git a/firmware/export/id3.h b/firmware/export/id3.h
index 7970f52..348b17e 100644
--- a/firmware/export/id3.h
+++ b/firmware/export/id3.h
@@ -53,13 +53,13 @@ enum {
struct mp3entry {
char path[MAX_PATH];
- char *title;
- char *artist;
- char *album;
- char* genre_string ;
- char* track_string ;
- char* year_string ;
- char* composer ;
+ char* title;
+ char* artist;
+ char* album;
+ char* genre_string;
+ char* track_string;
+ char* year_string;
+ char* composer;
int tracknum;
int version;
int layer;
@@ -115,6 +115,17 @@ struct mp3entry {
short voladjust;
long playcount;
long lastplayed;
+
+ /* replaygain support */
+
+#if CONFIG_HWCODEC == MASNONE
+ char* track_gain_str;
+ char* album_gain_str;
+ long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
+ long album_gain;
+ long track_peak; /* 7.24 signed fixed point. 0 for no peak. */
+ long album_peak;
+#endif
};
enum {