From 4a53787992b396d28c001f7567bc91644fae861c Mon Sep 17 00:00:00 2001 From: Magnus Holmgren Date: Sun, 24 Jul 2005 15:32:28 +0000 Subject: [PATCH] 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 --- apps/codecs/Tremor/ivorbisfile.h | 2 + apps/codecs/Tremor/vorbisfile.c | 44 ++++++++++ apps/codecs/lib/codeclib.c | 9 ++ apps/codecs/lib/codeclib.h | 2 +- apps/codecs/vorbis.c | 32 ++++--- apps/dsp.c | 138 +++++++++++++++++++++++++++++-- apps/dsp.h | 1 + apps/lang/english.lang | 53 ++++++++++++ apps/metadata.c | 42 ++++++++-- apps/playback.h | 6 +- apps/screens.c | 19 +++++ apps/settings.c | 3 + apps/settings.h | 6 ++ apps/settings_menu.c | 52 ++++++++++++ firmware/SOURCES | 3 + firmware/app.lds | 5 ++ firmware/export/id3.h | 25 ++++-- 17 files changed, 404 insertions(+), 38 deletions(-) diff --git a/apps/codecs/Tremor/ivorbisfile.h b/apps/codecs/Tremor/ivorbisfile.h index 1ec0f7428b..9ff446ab10 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 f6a208d1c7..0d8c04ab91 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_stateready_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 2494fe59f1..ac6e36245f 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 77276fbae2..3fc03bd122 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 8aa7f21a06..2976a05604 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),¤t_section); + + /* Read host-endian signed 24-bit PCM samples */ + n=ov_read_fixed(&vf,&pcm,1024,¤t_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 cd22610f74..ea0deceac0 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -16,11 +16,14 @@ * KIND, either express or implied. * ****************************************************************************/ +#include #include #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 723280bb6a..856c08f689 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 f2f38d2d62..f8c0c33e19 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: "" +voice "" +new: diff --git a/apps/metadata.c b/apps/metadata.c index 48bf637eb2..09bcb55a86 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 cb006f9e52..5b69228cbe 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 ab3ae0f817..440f9e129d 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 efb9cfd9de..5053c86b97 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 3e3a982b94..e0a61bf21d 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 5b03a8d22e..50f139637c 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 afb36f490d..5fbe6be20e 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 ef7cb19d33..0c9fa8d834 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 7970f529f1..348b17e191 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 {