diff --git a/Application/PlaybackController.h b/Application/PlaybackController.h index bebab0f12..830466f42 100644 --- a/Application/PlaybackController.h +++ b/Application/PlaybackController.h @@ -14,6 +14,7 @@ extern NSString *CogPlaybackDidPauseNotficiation; extern NSString *CogPlaybackDidResumeNotficiation; extern NSString *CogPlaybackDidStopNotficiation; +extern NSDictionary * makeRGInfo(PlaylistEntry *pe); @class PlaylistController; @class PlaylistView; diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index 3704651d5..b7883e0fb 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -117,6 +117,22 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation"; [self playEntryAtIndex:[playlistView selectedRow]]; } +NSDictionary * makeRGInfo(PlaylistEntry *pe) +{ + NSMutableDictionary * dictionary = [NSMutableDictionary dictionary]; + if ([pe replayGainAlbumGain] != 0) + [dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumGain]] forKey:@"replayGainAlbumGain"]; + if ([pe replayGainAlbumPeak] != 0) + [dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumPeak]] forKey:@"replayGainAlbumPeak"]; + if ([pe replayGainTrackGain] != 0) + [dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackGain]] forKey:@"replayGainTrackGain"]; + if ([pe replayGainTrackPeak] != 0) + [dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackPeak]] forKey:@"replayGainTrackPeak"]; + if ([pe volume] != 1) + [dictionary setObject:[NSNumber numberWithFloat:[pe volume]] forKey:@"volume"]; + return dictionary; +} + - (void)playEntry:(PlaylistEntry *)pe { if (playbackStatus != kCogStatusStopped) @@ -130,7 +146,7 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation"; if (pe == nil) return; - [audioPlayer play:[pe URL] withUserInfo:pe]; + [audioPlayer play:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe)]; } - (IBAction)next:(id)sender @@ -430,7 +446,7 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation"; else pe = [playlistController getNextEntry:curEntry]; - [player setNextStream:[pe URL] withUserInfo:pe]; + [player setNextStream:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe)]; } - (void)audioPlayer:(AudioPlayer *)player didBeginStream:(id)userInfo diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index 25bb69777..c12872dc4 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -22,6 +22,7 @@ NSURL *nextStream; id nextStreamUserInfo; + NSDictionary *nextStreamRGInfo; id delegate; @@ -35,7 +36,7 @@ - (id)delegate; - (void)play:(NSURL *)url; -- (void)play:(NSURL *)url withUserInfo:(id)userInfo; +- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi; - (void)stop; - (void)pause; @@ -50,9 +51,11 @@ - (double)amountPlayed; - (void)setNextStream:(NSURL *)url; -- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo; +- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary*)rgi; - (void)resetNextStreams; +- (void)setRGInfo:(NSDictionary *)rgi; + + (NSArray *)fileTypes; + (NSArray *)schemes; + (NSArray *)containerTypes; diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 7ca1823da..4b5bfd638 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -43,10 +43,10 @@ - (void)play:(NSURL *)url { - [self play:url withUserInfo:nil]; + [self play:url withUserInfo:nil withRGInfo:nil]; } -- (void)play:(NSURL *)url withUserInfo:(id)userInfo +- (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi { if (output) { @@ -91,6 +91,7 @@ } userInfo = nextStreamUserInfo; + rgi = nextStreamRGInfo; [self notifyStreamChanged:userInfo]; @@ -98,6 +99,7 @@ } [bufferChain setUserInfo:userInfo]; + [bufferChain setRGInfo:rgi]; [self setShouldContinue:YES]; @@ -152,10 +154,10 @@ //This is called by the delegate DURING a requestNextStream request. - (void)setNextStream:(NSURL *)url { - [self setNextStream:url withUserInfo:nil]; + [self setNextStream:url withUserInfo:nil withRGInfo:nil]; } -- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo +- (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi { [url retain]; [nextStream release]; @@ -164,7 +166,10 @@ [userInfo retain]; [nextStreamUserInfo release]; nextStreamUserInfo = userInfo; - + + [rgi retain]; + [nextStreamRGInfo release]; + nextStreamRGInfo = rgi; } // Called when the playlist changed before we actually started playing a requested stream. We will re-request. @@ -212,7 +217,7 @@ - (void)notifyStreamChanged:(id)userInfo { - [self sendDelegateMethod:@selector(audioPlayer:didBeginStream:) withObject:userInfo waitUntilDone:NO]; + [self sendDelegateMethod:@selector(audioPlayer:didBeginStream:) withObject:userInfo waitUntilDone:YES]; } - (void)addChainToQueue:(BufferChain *)newChain @@ -233,6 +238,9 @@ nextStreamUserInfo = [sender userInfo]; [nextStreamUserInfo retain]; //Retained because when setNextStream is called, it will be released!!! + nextStreamRGInfo = [sender rgInfo]; + [nextStreamRGInfo retain]; + [self requestNextStream: nextStreamUserInfo]; newChain = [[BufferChain alloc] initWithController:self]; diff --git a/Audio/Chain/BufferChain.h b/Audio/Chain/BufferChain.h index 361375a4d..ae3b46aaf 100644 --- a/Audio/Chain/BufferChain.h +++ b/Audio/Chain/BufferChain.h @@ -18,6 +18,7 @@ NSURL *streamURL; id userInfo; + NSDictionary *rgInfo; id finalNode; //Final buffer in the chain. @@ -43,6 +44,9 @@ - (id)userInfo; - (void)setUserInfo:(id)i; +- (NSDictionary*)rgInfo; +- (void)setRGInfo:(NSDictionary *)rgi; + - (NSURL *)streamURL; - (void)setStreamURL:(NSURL *)url; diff --git a/Audio/Chain/BufferChain.m b/Audio/Chain/BufferChain.m index 01673aa1a..deb2e46fb 100644 --- a/Audio/Chain/BufferChain.m +++ b/Audio/Chain/BufferChain.m @@ -21,6 +21,7 @@ controller = c; streamURL = nil; userInfo = nil; + rgInfo = nil; inputNode = nil; converterNode = nil; @@ -100,8 +101,22 @@ return userInfo; } +- (void)setRGInfo:(NSDictionary *)rgi +{ + [rgi retain]; + [rgInfo release]; + rgInfo = rgi; + [inputNode setRGInfo:rgi]; +} + +- (NSDictionary *)rgInfo +{ + return rgInfo; +} + - (void)dealloc { + [rgInfo release]; [userInfo release]; [streamURL release]; diff --git a/Audio/Chain/InputNode.h b/Audio/Chain/InputNode.h index e77987f73..3834a7e93 100644 --- a/Audio/Chain/InputNode.h +++ b/Audio/Chain/InputNode.h @@ -18,6 +18,7 @@ @interface InputNode : Node { id decoder; + NSDictionary * rgInfo; int bytesPerSample; int bytesPerFrame; @@ -39,6 +40,8 @@ - (BOOL)setTrack:(NSURL *)track; +- (void)setRGInfo:(NSDictionary *)rgi; + - (id) decoder; - (void)refreshVolumeScaling; diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index 352a5df2c..37102850c 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -133,11 +133,13 @@ static float db_to_scale(float db) - (void)refreshVolumeScaling { NSDictionary *properties = [decoder properties]; + if (rgInfo != nil) + properties = rgInfo; NSString * scaling = [[NSUserDefaults standardUserDefaults] stringForKey:@"volumeScaling"]; BOOL useAlbum = [scaling hasPrefix:@"albumGain"]; BOOL useTrack = useAlbum || [scaling hasPrefix:@"trackGain"]; BOOL useVolume = useAlbum || useTrack || [scaling isEqualToString:@"volumeScale"]; - bool usePeak = [scaling hasSuffix:@"WithPeak"]; + BOOL usePeak = [scaling hasSuffix:@"WithPeak"]; float scale = 1.0; float peak = 0.0; if (useVolume) { @@ -319,9 +321,19 @@ static int32_t swap_32(uint32_t input) return NO; } +- (void)setRGInfo:(NSDictionary *)i +{ + [i retain]; + [rgInfo release]; + rgInfo = i; + [self refreshVolumeScaling]; +} + - (void)dealloc { NSLog(@"Input Node dealloc"); + + [rgInfo release]; [decoder removeObserver:self forKeyPath:@"properties"]; [decoder removeObserver:self forKeyPath:@"metadata"]; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp index 1c38d2bdd..fffa8debe 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp @@ -133,6 +133,34 @@ TagLib::uint APE::Tag::track() const return d->itemListMap["TRACK"].toString().toInt(); } +float APE::Tag::rgAlbumGain() const +{ + if (d->itemListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) + return 0; + return d->itemListMap["REPLAYGAIN_ALBUM_GAIN"].toString().toFloat(); +} + +float APE::Tag::rgAlbumPeak() const +{ + if (d->itemListMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty()) + return 0; + return d->itemListMap["REPLAYGAIN_ALBUM_PEAK"].toString().toFloat(); +} + +float APE::Tag::rgTrackGain() const +{ + if (d->itemListMap["REPLAYGAIN_TRACK_GAIN"].isEmpty()) + return 0; + return d->itemListMap["REPLAYGAIN_TRACK_GAIN"].toString().toFloat(); +} + +float APE::Tag::rgTrackPeak() const +{ + if (d->itemListMap["REPLAYGAIN_TRACK_PEAK"].isEmpty()) + return 0; + return d->itemListMap["REPLAYGAIN_TRACK_PEAK"].toString().toFloat(); +} + void APE::Tag::setTitle(const String &s) { addValue("TITLE", s, true); @@ -174,6 +202,38 @@ void APE::Tag::setTrack(uint i) addValue("TRACK", String::number(i), true); } +void APE::Tag::setRGAlbumGain(float f) +{ + if (f == 0) + removeItem("REPLAYGAIN_ALBUM_GAIN"); + else + addValue("REPLAYGAIN_ALBUM_GAIN", String::number(f) + " dB", true); +} + +void APE::Tag::setRGAlbumPeak(float f) +{ + if (f == 0) + removeItem("REPLAYGAIN_ALBUM_PEAK"); + else + addValue("REPLAYGAIN_ALBUM_PEAK", String::number(f), true); +} + +void APE::Tag::setRGTrackGain(float f) +{ + if (f == 0) + removeItem("REPLAYGAIN_TRACK_GAIN"); + else + addValue("REPLAYGAIN_TRACK_GAIN", String::number(f) + " dB", true); +} + +void APE::Tag::setRGTrackPeak(float f) +{ + if (f == 0) + removeItem("REPLAYGAIN_TRACK_PEAK"); + else + addValue("REPLAYGAIN_TRACK_PEAK", String::number(f), true); +} + APE::Footer *APE::Tag::footer() const { return &d->footer; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.h b/Frameworks/TagLib/taglib/taglib/ape/apetag.h index 03a3c9177..492114ab5 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.h @@ -94,6 +94,10 @@ namespace TagLib { virtual String genre() const; virtual uint year() const; virtual uint track() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); @@ -102,6 +106,10 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(uint i); virtual void setTrack(uint i); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); /*! * Returns a pointer to the tag's footer. diff --git a/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.cpp b/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.cpp index 341b91c35..29827ae47 100755 --- a/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.cpp +++ b/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.cpp @@ -19,6 +19,10 @@ public: TagLib::uint disk; TagLib::uint numDisks; TagLib::uint bpm; + float rgAlbumGain; + float rgAlbumPeak; + float rgTrackGain; + float rgTrackPeak; bool isEmpty; TagLib::ByteVector cover; }; @@ -76,6 +80,26 @@ TagLib::uint MP4::Tag::track() const return d->track; } +float MP4::Tag::rgAlbumGain() const +{ + return d->rgAlbumGain; +} + +float MP4::Tag::rgAlbumPeak() const +{ + return d->rgAlbumPeak; +} + +float MP4::Tag::rgTrackGain() const +{ + return d->rgTrackGain; +} + +float MP4::Tag::rgTrackPeak() const +{ + return d->rgTrackPeak; +} + TagLib::uint MP4::Tag::numTracks() const { return d->numTracks; @@ -153,6 +177,30 @@ void MP4::Tag::setTrack(TagLib::uint i) d->isEmpty = false; } +void MP4::Tag::setRGAlbumGain(float f) +{ + d->rgAlbumGain = f; + d->isEmpty = false; +} + +void MP4::Tag::setRGAlbumPeak(float f) +{ + d->rgAlbumPeak = f; + d->isEmpty = false; +} + +void MP4::Tag::setRGTrackGain(float f) +{ + d->rgTrackGain = f; + d->isEmpty = false; +} + +void MP4::Tag::setRGTrackPeak(float f) +{ + d->rgTrackPeak = f; + d->isEmpty = false; +} + void MP4::Tag::setNumTracks(TagLib::uint i) { d->numTracks = i; diff --git a/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.h b/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.h index 7f0d6a050..1fe86bddd 100755 --- a/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.h +++ b/Frameworks/TagLib/taglib/taglib/m4a/mp4itunestag.h @@ -33,6 +33,10 @@ namespace TagLib virtual String genre() const; virtual uint year() const; virtual uint track() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); @@ -41,6 +45,10 @@ namespace TagLib virtual void setGenre(const String &s); virtual void setYear(uint i); virtual void setTrack(uint i); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); // MP4 specific fields diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp index 490f8ddaf..9d2311162 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp @@ -152,6 +152,26 @@ TagLib::uint ID3v1::Tag::track() const return d->track; } +float ID3v1::Tag::rgAlbumGain() const +{ + return 0; +} + +float ID3v1::Tag::rgAlbumPeak() const +{ + return 0; +} + +float ID3v1::Tag::rgTrackGain() const +{ + return 0; +} + +float ID3v1::Tag::rgTrackPeak() const +{ + return 0; +} + void ID3v1::Tag::setTitle(const String &s) { d->title = s; @@ -187,6 +207,22 @@ void ID3v1::Tag::setTrack(uint i) d->track = i < 256 ? i : 0; } +void ID3v1::Tag::setRGAlbumGain(float) +{ +} + +void ID3v1::Tag::setRGAlbumPeak(float) +{ +} + +void ID3v1::Tag::setRGTrackGain(float) +{ +} + +void ID3v1::Tag::setRGTrackPeak(float) +{ +} + void ID3v1::Tag::setStringHandler(const StringHandler *handler) { delete TagPrivate::stringHandler; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h index 8dc60c3d2..482736ea8 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h @@ -140,6 +140,10 @@ namespace TagLib { virtual String genre() const; virtual uint year() const; virtual uint track() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); @@ -148,6 +152,10 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(uint i); virtual void setTrack(uint i); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); /*! * Sets the string handler that decides how the ID3v1 data will be diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp index beb496c8a..aca502f1c 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp @@ -195,6 +195,39 @@ TagLib::uint ID3v2::Tag::track() const return 0; } +float ID3v2::Tag::rg(const String &type) const +{ + const FrameList &list = d->frameListMap["TXXX"]; + if (!list.isEmpty()) { + for (FrameList::ConstIterator it = list.begin(); it != list.end(); ++it) { + if (static_cast(*it)->description() == type) { + return static_cast(*it)->toString().toFloat(); + } + } + } + return 0; +} + +float ID3v2::Tag::rgAlbumGain() const +{ + return rg("replaygain_album_gain"); +} + +float ID3v2::Tag::rgAlbumPeak() const +{ + return rg("replaygain_album_peak"); +} + +float ID3v2::Tag::rgTrackGain() const +{ + return rg("replaygain_track_gain"); +} + +float ID3v2::Tag::rgTrackPeak() const +{ + return rg("replaygain_track_peak"); +} + void ID3v2::Tag::setTitle(const String &s) { setTextFrame("TIT2", s); @@ -270,6 +303,52 @@ void ID3v2::Tag::setTrack(uint i) setTextFrame("TRCK", String::number(i)); } +void ID3v2::Tag::setRG(const String &type, float f, bool peak) +{ + bool createdFrame = false; + UserTextIdentificationFrame * frame = NULL; + FrameList &list = d->frameListMap["TXXX"]; + for (FrameList::Iterator it = list.begin(); it != list.end(); ++it) { + if (static_cast(*it)->description() == type) { + frame = static_cast(*it); + break; + } + } + if (f == 0) { + if (frame) + removeFrame(frame); + return; + } + if (frame == NULL) { + frame = new UserTextIdentificationFrame; + frame->setDescription(type); + createdFrame = true; + } + frame->setText(String::number(f) + (peak ? "" : " dB")); + if (createdFrame) + addFrame(frame); +} + +void ID3v2::Tag::setRGAlbumGain(float f) +{ + setRG("replaygain_album_gain", f, false); +} + +void ID3v2::Tag::setRGAlbumPeak(float f) +{ + setRG("replaygain_album_peak", f, true); +} + +void ID3v2::Tag::setRGTrackGain(float f) +{ + setRG("replaygain_track_gain", f, false); +} + +void ID3v2::Tag::setRGTrackPeak(float f) +{ + setRG("replaygain_track_peak", f, true); +} + bool ID3v2::Tag::isEmpty() const { return d->frameList.isEmpty(); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h index f43f6b768..192381987 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h @@ -142,6 +142,12 @@ namespace TagLib { virtual uint year() const; virtual uint track() const; + float rg(const String &type) const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; + virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); @@ -150,6 +156,12 @@ namespace TagLib { virtual void setYear(uint i); virtual void setTrack(uint i); + void setRG(const String &type, float f, bool peak); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); + virtual bool isEmpty() const; /*! diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp index d7c5c5c4d..9ea9e26bf 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp @@ -115,6 +115,34 @@ TagLib::uint Ogg::XiphComment::track() const return d->fieldListMap["TRACKNUMBER"].front().toInt(); } +float Ogg::XiphComment::rgAlbumGain() const +{ + if(d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) + return 0; + return d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].front().toFloat(); +} + +float Ogg::XiphComment::rgAlbumPeak() const +{ + if(d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty()) + return 0; + return d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].front().toFloat(); +} + +float Ogg::XiphComment::rgTrackGain() const +{ + if(d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].isEmpty()) + return 0; + return d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].front().toFloat(); +} + +float Ogg::XiphComment::rgTrackPeak() const +{ + if(d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].isEmpty()) + return 0; + return d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].front().toFloat(); +} + void Ogg::XiphComment::setTitle(const String &s) { addField("TITLE", s); @@ -156,6 +184,38 @@ void Ogg::XiphComment::setTrack(uint i) addField("TRACKNUMBER", String::number(i)); } +void Ogg::XiphComment::setRGAlbumGain(float f) +{ + if (f == 0) + removeField("REPLAYGAIN_ALBUM_GAIN"); + else + addField("REPLAYGAIN_ALBUM_GAIN", String::number(f) + " dB"); +} + +void Ogg::XiphComment::setRGAlbumPeak(float f) +{ + if (f == 0) + removeField("REPLAYGAIN_ALBUM_PEAK"); + else + addField("REPLAYGAIN_ALBUM_PEAK", String::number(f)); +} + +void Ogg::XiphComment::setRGTrackGain(float f) +{ + if (f == 0) + removeField("REPLAYGAIN_TRACK_GAIN"); + else + addField("REPLAYGAIN_TRACK_GAIN", String::number(f) + " dB"); +} + +void Ogg::XiphComment::setRGTrackPeak(float f) +{ + if (f == 0) + removeField("REPLAYGAIN_TRACK_PEAK"); + else + addField("REPLAYGAIN_TRACK_PEAK", String::number(f)); +} + bool Ogg::XiphComment::isEmpty() const { FieldListMap::ConstIterator it = d->fieldListMap.begin(); diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h index 818b3f41b..04f147579 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h @@ -86,6 +86,10 @@ namespace TagLib { virtual String genre() const; virtual uint year() const; virtual uint track() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); @@ -94,6 +98,10 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(uint i); virtual void setTrack(uint i); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); virtual bool isEmpty() const; diff --git a/Frameworks/TagLib/taglib/taglib/tag.h b/Frameworks/TagLib/taglib/taglib/tag.h index 6404a709c..102a4cf6d 100644 --- a/Frameworks/TagLib/taglib/taglib/tag.h +++ b/Frameworks/TagLib/taglib/taglib/tag.h @@ -90,7 +90,31 @@ namespace TagLib { * return 0. */ virtual uint track() const = 0; + + /*! + * Returns the ReplayGain album gain; if there is no gain level set, this + * will return 0. + */ + virtual float rgAlbumGain() const = 0; + /*! + * Returns the ReplayGain album peak; if there is no gain level set, this + * will return 0. + */ + virtual float rgAlbumPeak() const = 0; + + /*! + * Returns the ReplayGain track gain; if there is no gain level set, this + * will return 0. + */ + virtual float rgTrackGain() const = 0; + + /*! + * Returns the ReplayGain track peak; if there is no gain level set, this + * will return 0. + */ + virtual float rgTrackPeak() const = 0; + /*! * Sets the title to \a s. If \a s is String::null then this value will be * cleared. @@ -133,7 +157,31 @@ namespace TagLib { * Sets the track to \a i. If \a s is 0 then this value will be cleared. */ virtual void setTrack(uint i) = 0; + + /*! + * Sets the ReplayGain album gain to \a f. If \a f is 0 then this value will + * be cleared. + */ + virtual void setRGAlbumGain(float i) = 0; + /*! + * Sets the ReplayGain album peak to \a f. If \a f is 0 then this value will + * be cleared. + */ + virtual void setRGAlbumPeak(float i) = 0; + + /*! + * Sets the ReplayGain track gain to \a f. If \a f is 0 then this value will + * be cleared. + */ + virtual void setRGTrackGain(float i) = 0; + + /*! + * Sets the ReplayGain track peak to \a f. If \a f is 0 then this value will + * be cleared. + */ + virtual void setRGTrackPeak(float i) = 0; + /*! * Returns true if the tag does not contain any data. This should be * reimplemented in subclasses that provide more than the basic tagging diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.cpp b/Frameworks/TagLib/taglib/taglib/tagunion.cpp index 08cc14083..2a9ce8846 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.cpp +++ b/Frameworks/TagLib/taglib/taglib/tagunion.cpp @@ -45,6 +45,15 @@ using namespace TagLib; return tag(2)->method(); \ return 0 +#define floatUnion(method) \ + if(tag(0) && tag(0)->method() != 0) \ + return tag(0)->method(); \ + if(tag(1) && tag(1)->method() != 0) \ + return tag(1)->method(); \ + if(tag(2) && tag(2)->method() != 0) \ + return tag(2)->method(); \ +return 0 + #define setUnion(method, value) \ if(tag(0)) \ tag(0)->set##method(value); \ @@ -136,6 +145,26 @@ TagLib::uint TagUnion::track() const numberUnion(track); } +float TagUnion::rgAlbumGain() const +{ + floatUnion(rgAlbumGain); +} + +float TagUnion::rgAlbumPeak() const +{ + floatUnion(rgAlbumPeak); +} + +float TagUnion::rgTrackGain() const +{ + floatUnion(rgTrackGain); +} + +float TagUnion::rgTrackPeak() const +{ + floatUnion(rgTrackPeak); +} + void TagUnion::setTitle(const String &s) { setUnion(Title, s); @@ -171,6 +200,26 @@ void TagUnion::setTrack(uint i) setUnion(Track, i); } +void TagUnion::setRGAlbumGain(float f) +{ + setUnion(RGAlbumGain, f); +} + +void TagUnion::setRGAlbumPeak(float f) +{ + setUnion(RGAlbumPeak, f); +} + +void TagUnion::setRGTrackGain(float f) +{ + setUnion(RGTrackGain, f); +} + +void TagUnion::setRGTrackPeak(float f) +{ + setUnion(RGTrackPeak, f); +} + bool TagUnion::isEmpty() const { if(d->tags[0] && !d->tags[0]->isEmpty()) diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.h b/Frameworks/TagLib/taglib/taglib/tagunion.h index 76d407ce3..15753da16 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.h +++ b/Frameworks/TagLib/taglib/taglib/tagunion.h @@ -63,6 +63,10 @@ namespace TagLib { virtual String genre() const; virtual uint year() const; virtual uint track() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); @@ -71,6 +75,10 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(uint i); virtual void setTrack(uint i); + virtual void setRGAlbumGain(float f); + virtual void setRGAlbumPeak(float f); + virtual void setRGTrackGain(float f); + virtual void setRGTrackPeak(float f); virtual bool isEmpty() const; template T *access(int index, bool create) diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp index cd274367e..644dbe4e0 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp @@ -31,6 +31,8 @@ #include +#include + namespace TagLib { inline unsigned short byteSwap(unsigned short x) @@ -435,6 +437,31 @@ int String::toInt() const return value; } +float String::toFloat() const +{ + float value = 0; + float decimal_value = 1; + + bool negative = d->data[0] == '-'; + uint i = negative ? 1 : 0; + + for(; i < d->data.size() && d->data[i] >= '0' && d->data[i] <= '9'; i++) + value = value * 10 + (d->data[i] - '0'); + + if (i < d->data.size() && d->data[i] == '.') + for(++i; i < d->data.size() && d->data[i] >= '0' && d->data[i] <= '9'; i++) { + value = value * 10 + (d->data[i] - '0'); + decimal_value *= 0.1; + } + + if(negative) + value = value * -1; + + value *= decimal_value; + + return value; +} + String String::stripWhiteSpace() const { wstring::const_iterator begin = d->data.begin(); @@ -479,6 +506,11 @@ bool String::isAscii() const return true; } +String String::number(uint n) +{ + return number((int)n); +} + String String::number(int n) // static { if(n == 0) @@ -508,6 +540,48 @@ String String::number(int n) // static return s; } +String String::number(float n) // static +{ + if(n == 0) + return String("0"); + + float decimal = fmod(n, 1); + n -= decimal; + + String charStack; + + bool negative = n < 0; + + if(negative) + n = n * -1; + + while(n > 0) { + float remainder = fmod(n, 10); + charStack += char(remainder + '0'); + n = (n - remainder) / 10; + } + + String s; + + if(negative) + s += '-'; + + for(int i = charStack.d->data.size() - 1; i >= 0; i--) + s += charStack.d->data[i]; + + if (decimal > 0) { + s += '.'; + while (decimal > 0) { + decimal *= 10; + float remainder = fmod(decimal, 1); + s += char(decimal + '0'); + decimal = remainder; + } + } + + return s; +} + TagLib::wchar &String::operator[](int i) { return d->data[i]; diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h index 660023bad..15b575862 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h @@ -286,6 +286,11 @@ namespace TagLib { * Convert the string to an integer. */ int toInt() const; + + /*! + * Convert the string to a float. + */ + float toFloat() const; /*! * Returns a string with the leading and trailing whitespace stripped. @@ -305,7 +310,13 @@ namespace TagLib { /*! * Converts the base-10 integer \a n to a string. */ + static String number(uint n); static String number(int n); + + /*! + * Converts the base-10 float \a n to a string. + */ + static String number(float n); /*! * Returns a reference to the character at position \a i. diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index d105ce81a..4dafe50da 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -39,6 +39,12 @@ @synthesize bitsPerSample; @synthesize sampleRate; +@synthesize replayGainAlbumGain; +@synthesize replayGainAlbumPeak; +@synthesize replayGainTrackGain; +@synthesize replayGainTrackPeak; +@synthesize volume; + @synthesize endian; @synthesize seekable; @@ -82,6 +88,18 @@ return [NSString stringWithFormat:@"PlaylistEntry %i:(%@)", self.index, self.URL]; } +- (id)init +{ + if (self = [super init]) { + self.replayGainAlbumGain = 0; + self.replayGainAlbumPeak = 0; + self.replayGainTrackGain = 0; + self.replayGainTrackPeak = 0; + self.volume = 1; + } + return self; +} + - (void)dealloc { self.errorMessage = nil; diff --git a/Plugins/TagLib/TagLibMetadataReader.m b/Plugins/TagLib/TagLibMetadataReader.m index cb6a1a97d..ff6672de0 100644 --- a/Plugins/TagLib/TagLibMetadataReader.m +++ b/Plugins/TagLib/TagLibMetadataReader.m @@ -32,6 +32,7 @@ { TagLib::String artist, title, album, genre, comment; int year, track; + float rgAlbumGain, rgAlbumPeak, rgTrackGain, rgTrackPeak; artist = tag->artist(); title = tag->title();; @@ -44,6 +45,15 @@ track = tag->track(); [dict setObject:[NSNumber numberWithInt:track] forKey:@"track"]; + + rgAlbumGain = tag->rgAlbumGain(); + rgAlbumPeak = tag->rgAlbumPeak(); + rgTrackGain = tag->rgTrackGain(); + rgTrackPeak = tag->rgTrackPeak(); + [dict setObject:[NSNumber numberWithFloat:rgAlbumGain] forKey:@"replayGainAlbumGain"]; + [dict setObject:[NSNumber numberWithFloat:rgAlbumPeak] forKey:@"replayGainAlbumPeak"]; + [dict setObject:[NSNumber numberWithFloat:rgTrackGain] forKey:@"replayGainTrackGain"]; + [dict setObject:[NSNumber numberWithFloat:rgTrackPeak] forKey:@"replayGainTrackPeak"]; if (!artist.isNull()) [dict setObject:[NSString stringWithUTF8String:artist.toCString(true)] forKey:@"artist"];