Index: ChangeLog
===================================================================
--- ChangeLog	(revision 5623)
+++ ChangeLog	(revision 5624)
@@ -7,4 +7,5 @@
 	* Added meta generes (See: #301)
 	* Fixed metadata handling in meta.c.
+	* Added support for RePlayGain (RPG) control (See: #300)
 
 v. 1.0beta4 - Mon Aug 06 2012 16:43 CEST
Index: include/libroar/stream.h
===================================================================
--- include/libroar/stream.h	(revision 5535)
+++ include/libroar/stream.h	(revision 5624)
@@ -57,4 +57,10 @@
 };
 
+struct roar_stream_rpg {
+ int        mode;
+ uint16_t   mul;
+ uint16_t   div;
+};
+
 int roar_stream_connect (struct roar_connection * con, struct roar_stream * s, int dir, int mixer) _LIBROAR_STREAM_STDATTRS;
 
@@ -95,4 +101,7 @@
 int roar_stream_set_role  (struct roar_connection * con, struct roar_stream * s, int role) _LIBROAR_STREAM_STDATTRS;
 
+int roar_stream_get_rpg   (struct roar_connection * con, struct roar_stream * s, struct roar_stream_rpg * rpg) _LIBROAR_STREAM_STDATTRS;
+int roar_stream_set_rpg   (struct roar_connection * con, struct roar_stream * s, const struct roar_stream_rpg * rpg) _LIBROAR_STREAM_STDATTRS;
+
 int roar_stream_s2m     (struct roar_stream * s, struct roar_message * m);
 int roar_stream_m2s     (struct roar_stream * s, struct roar_message * m);
@@ -113,4 +122,6 @@
 const char * roar_role2str  (const int    role) _LIBROAR_ATTR_USE_RESULT _LIBROAR_ATTR_PURE;
 
+const char * roar_rpgmode2str(const int rpgmode) _LIBROAR_ATTR_USE_RESULT _LIBROAR_ATTR_PURE;
+
 ssize_t roar_info2samplesize (struct roar_audio_info * info) _LIBROAR_STREAM_STDATTRS _LIBROAR_ATTR_PURE;
 ssize_t roar_info2framesize  (struct roar_audio_info * info) _LIBROAR_STREAM_STDATTRS _LIBROAR_ATTR_PURE;
Index: include/roaraudio/proto.h
===================================================================
--- include/roaraudio/proto.h	(revision 5464)
+++ include/roaraudio/proto.h	(revision 5624)
@@ -168,4 +168,14 @@
 #define ROAR_STREAM_PARA_USAGE     10 /* used by RAUM */
 #define ROAR_STREAM_PARA_RADIO     11 /* used by RDTCS */
+#define ROAR_STREAM_PARA_RPG       12
+
+// modes for ROAR_STREAM_PARA_RPG:
+#define ROAR_RPGMODE_DEFAULT       -1
+#define ROAR_RPGMODE_NONE           0
+#define ROAR_RPGMODE_USER           1
+#define ROAR_RPGMODE_ALBUM          2
+#define ROAR_RPGMODE_TRACK          3
+#define ROAR_RPGMODE_ALBUMTRACK     4
+#define ROAR_RPGMODE_TRACKALBUM     5
 
 // Message flags:
Index: libroar/stream.c
===================================================================
--- libroar/stream.c	(revision 5542)
+++ libroar/stream.c	(revision 5624)
@@ -743,4 +743,90 @@
 }
 
+int roar_stream_get_rpg   (struct roar_connection * con, struct roar_stream * s, struct roar_stream_rpg * rpg) {
+ struct roar_message m;
+ uint16_t * data = (uint16_t *) m.data;
+ size_t i;
+
+ memset(&m,  0, sizeof(m));
+
+ m.cmd     = ROAR_CMD_GET_STREAM_PARA;
+ m.stream  = s->id;
+ m.datalen = 2*2;
+ m.pos     = 0;
+
+ data[0] = 0; // Version and reserved
+ data[1] = ROAR_STREAM_PARA_RPG; // flags
+
+ for (i = 0; i < m.datalen/2; i++) {
+  data[i] = ROAR_HOST2NET16(data[i]);
+ }
+
+ if ( roar_req(con, &m, NULL) == -1 )
+  return -1;
+
+ if ( m.cmd != ROAR_CMD_OK )
+  return -1;
+
+ for (i = 0; i < m.datalen/2; i++) {
+  data[i] = ROAR_NET2HOST16(data[i]);
+ }
+
+ if ( m.datalen != 10 ) {
+  roar_err_set(ROAR_ERROR_MSGSIZE);
+  return -1;
+ }
+
+ if ( data[0] != 0 ) {
+  roar_err_set(ROAR_ERROR_NSVERSION);
+  return -1;
+ }
+
+ if ( data[1] != ROAR_STREAM_PARA_RPG ) {
+  roar_err_set(ROAR_ERROR_BADRQC);
+  return -1;
+ }
+
+ memset(rpg, 0, sizeof(struct roar_stream_rpg));
+
+ rpg->mode = data[2];
+ rpg->mul  = data[3];
+ rpg->div  = data[4];
+
+ if ( rpg->mode == 0xFFFF )
+  rpg->mode = -1;
+
+ return 0;
+}
+
+int roar_stream_set_rpg   (struct roar_connection * con, struct roar_stream * s, const struct roar_stream_rpg * rpg) {
+ struct roar_message m;
+ uint16_t * data = (uint16_t *) m.data;
+ size_t i;
+
+ memset(&m,  0, sizeof(m));
+
+ m.cmd     = ROAR_CMD_SET_STREAM_PARA;
+ m.stream  = s->id;
+ m.datalen = (2+3)*2;
+ m.pos     = 0;
+
+ data[0] = 0; // Version and reserved
+ data[1] = ROAR_STREAM_PARA_RPG; // flags
+ data[2] = rpg->mode;
+ data[3] = rpg->mul;
+ data[4] = rpg->div;
+
+ for (i = 0; i < m.datalen/2; i++) {
+  data[i] = ROAR_HOST2NET16(data[i]);
+ }
+
+ if ( roar_req(con, &m, NULL) == -1 )
+  return -1;
+
+ if ( m.cmd != ROAR_CMD_OK )
+  return -1;
+
+ return 0;
+}
 
 #ifdef DEBUG
@@ -1168,4 +1254,20 @@
 
  return "unknown";
+}
+
+const char * roar_rpgmode2str(const int rpgmode) {
+ switch (rpgmode) {
+  case ROAR_RPGMODE_DEFAULT:    return "default"; break;
+  case ROAR_RPGMODE_NONE:       return "none"; break;
+  case ROAR_RPGMODE_USER:       return "user"; break;
+  case ROAR_RPGMODE_ALBUM:      return "album"; break;
+  case ROAR_RPGMODE_TRACK:      return "track"; break;
+  case ROAR_RPGMODE_ALBUMTRACK: return "albumtrack"; break;
+  case ROAR_RPGMODE_TRACKALBUM: return "trackalbum"; break;
+  default:
+    roar_err_set(ROAR_ERROR_NOENT);
+    return NULL;
+   break;
+ }
 }
 
Index: roarclients/roarctl.c
===================================================================
--- roarclients/roarctl.c	(revision 5537)
+++ roarclients/roarctl.c	(revision 5624)
@@ -763,5 +763,7 @@
 
 int display_mixer (struct roar_connection * con, int stream) {
+ struct roar_stream_rpg rpg;
  struct roar_mixer_settings mixer;
+ struct roar_stream s;
  int channels;
  int i;
@@ -771,4 +773,9 @@
   fprintf(stderr, "Error: can not get stream mixer info for stream %i\n", stream);
   return -1;
+ }
+
+ roar_stream_new_by_id(&s, stream);
+ if ( roar_stream_get_rpg(con, &s, &rpg) != -1 ) {
+  printf("ReplayGain Mode       : %s\n", roar_rpgmode2str(rpg.mode));
  }
 
Index: roard/codecfilter_flac.c
===================================================================
--- roard/codecfilter_flac.c	(revision 5381)
+++ roard/codecfilter_flac.c	(revision 5624)
@@ -116,8 +116,9 @@
  const FLAC__StreamMetadata_VorbisComment * vc;
  FLAC__uint32 i;
+ struct roar_stream_rpg rpg;
  const char * key, * value;
  char keycopy[ROAR_META_MAX_NAMELEN];
  int type;
- float rpg_track = 0, rpg_album = 0;
+ float rpg_track = 0, rpg_album = 0, rpg_final = 0;
 
  if ( metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT ) {
@@ -171,10 +172,23 @@
   }
 
- if ( rpg_album ) {
-  self->ss->mixer.rpg_div = 2718;  // = int(exp(1)*1000)
-  self->ss->mixer.rpg_mul = (float)rpg_album*2718;
- } else if ( rpg_track ) {
-  self->ss->mixer.rpg_div = 2718;
-  self->ss->mixer.rpg_mul = (float)rpg_track*2718;
+ if ( streams_get_rpg(ROAR_STREAM(self->ss)->id, &rpg) != -1 ) {
+  rpg_final = 0;
+
+  switch (rpg.mode) {
+   case ROAR_RPGMODE_NONE: rpg_final =  0.; break;
+   case ROAR_RPGMODE_USER: rpg_final = -1.; break;
+   case ROAR_RPGMODE_ALBUM: rpg_final = rpg_album; break;
+   case ROAR_RPGMODE_TRACK: rpg_final = rpg_track; break;
+   case ROAR_RPGMODE_ALBUMTRACK: rpg_final = rpg_album ? rpg_album : rpg_track; break;
+   case ROAR_RPGMODE_TRACKALBUM: rpg_final = rpg_track ? rpg_track : rpg_album; break;
+  }
+
+  if ( rpg_final > 0 ) {
+   self->ss->mixer.rpg_div = 2718;
+   self->ss->mixer.rpg_mul = (float)rpg_final*2718;
+  } else if ( !rpg_final ) {
+   self->ss->mixer.rpg_div = 1;
+   self->ss->mixer.rpg_mul = 1;
+  }
  }
 
Index: roard/codecfilter_vorbis.c
===================================================================
--- roard/codecfilter_vorbis.c	(revision 5381)
+++ roard/codecfilter_vorbis.c	(revision 5624)
@@ -305,7 +305,8 @@
  char key[ROAR_META_MAX_NAMELEN] = {0}, value[LIBROAR_BUFFER_MSGDATA] = {0};
  struct roar_stream * s = ROAR_STREAM(self->stream);
+ struct roar_stream_rpg rpg;
  int type;
  int j, h = 0;
- float rpg_track = 0, rpg_album = 0;
+ float rpg_track = 0, rpg_album = 0, rpg_final;
  int meta_ok;
 
@@ -373,10 +374,23 @@
  }
 
- if ( rpg_album ) {
-  self->stream->mixer.rpg_div = 2718;  // = int(exp(1)*1000)
-  self->stream->mixer.rpg_mul = (float)rpg_album*2718;
- } else if ( rpg_track ) {
-  self->stream->mixer.rpg_div = 2718;
-  self->stream->mixer.rpg_mul = (float)rpg_track*2718;
+ if ( streams_get_rpg(ROAR_STREAM(self->stream)->id, &rpg) != -1 ) {
+  rpg_final = 0;
+
+  switch (rpg.mode) {
+   case ROAR_RPGMODE_NONE: rpg_final =  0.; break;
+   case ROAR_RPGMODE_USER: rpg_final = -1.; break;
+   case ROAR_RPGMODE_ALBUM: rpg_final = rpg_album; break;
+   case ROAR_RPGMODE_TRACK: rpg_final = rpg_track; break;
+   case ROAR_RPGMODE_ALBUMTRACK: rpg_final = rpg_album ? rpg_album : rpg_track; break;
+   case ROAR_RPGMODE_TRACKALBUM: rpg_final = rpg_track ? rpg_track : rpg_album; break;
+  }
+
+  if ( rpg_final > 0 ) {
+   self->stream->mixer.rpg_div = 2718;
+   self->stream->mixer.rpg_mul = (float)rpg_final*2718;
+  } else if ( !rpg_final ) {
+   self->stream->mixer.rpg_div = 1;
+   self->stream->mixer.rpg_mul = 1;
+  }
  }
 
Index: roard/include/roard.h
===================================================================
--- roard/include/roard.h	(revision 5414)
+++ roard/include/roard.h	(revision 5624)
@@ -196,7 +196,8 @@
  uint32_t flags;
  struct {
-  uint32_t flags;
+  uint32_t                   flags;
   struct roar_mixer_settings mixer;
   int                        mixer_channels;
+  int                        rpgmode;
  } streams[ROAR_DIR_DIRIDS];
  char * location;
Index: roard/include/streams.h
===================================================================
--- roard/include/streams.h	(revision 5381)
+++ roard/include/streams.h	(revision 5624)
@@ -116,4 +116,5 @@
  size_t single_sink_c;
  size_t single_sink_self_c;
+ int rpgmode;
 } * g_streams[ROAR_STREAMS_MAX];
 
@@ -177,4 +178,7 @@
 int streams_set_map      (int id, char * map, size_t len);
 
+int streams_get_rpg      (int id, struct roar_stream_rpg * rpg);
+int streams_set_rpg      (int id, const struct roar_stream_rpg * rpg);
+
 int streams_ltm_ctl      (int id, int mt, int window, int cmd);
 int streams_ltm_calc     (int id, struct roar_audio_info * info, void * data, size_t len);
Index: roard/req.c
===================================================================
--- roard/req.c	(revision 5608)
+++ roard/req.c	(revision 5624)
@@ -1118,4 +1118,5 @@
  struct roar_audio_info * audio_info;
  struct roar_stream_ltm * ltm;
+ struct roar_stream_rpg   rpg;
  uint16_t * d  = (uint16_t *) mes->data;
  int64_t * d64 = ( int64_t *) mes->data;
@@ -1348,4 +1349,20 @@
    break;
 
+  case ROAR_STREAM_PARA_RPG:
+    if ( streams_get_rpg(mes->stream, &rpg) == -1 ) {
+     return -1;
+    }
+
+    mes->datalen = 2*5;
+
+    d[2] = rpg.mode;
+    d[3] = rpg.mul;
+    d[4] = rpg.div;
+
+    for (i = 0; i < mes->datalen/2; i++) {
+     d[i] = ROAR_HOST2NET16(d[i]);
+    }
+   break;
+
   default:
     ROAR_WARN("req_on_get_stream_para(*): unsupported command: %i", d[1]);
@@ -1360,4 +1377,5 @@
 int req_on_set_stream_para (int client, struct roar_message * mes, char ** data, uint32_t flags[2]) {
  struct roar_stream_server * ss;
+ struct roar_stream_rpg rpg;
  uint16_t * d = (uint16_t *) mes->data;
  uint32_t tmp, tmp2, flagstore;
@@ -1470,4 +1488,19 @@
     }
    break;
+  case ROAR_STREAM_PARA_RPG:
+    if ( mes->datalen != (5 * 2) )
+     return -1;
+
+    for (i = 2; i < mes->datalen/2; i++) {
+     d[i] = ROAR_NET2HOST16(d[i]);
+    }
+
+    rpg.mode = d[2];
+    rpg.mul  = d[3];
+    rpg.div  = d[4];
+
+    if ( streams_set_rpg(mes->stream, &rpg) == -1 )
+     return -1;
+   break;
   default:
     ROAR_WARN("req_on_set_stream_para(*): unsupported command version: %i, %i", d[0], d[1]);
Index: roard/roard.c
===================================================================
--- roard/roard.c	(revision 5619)
+++ roard/roard.c	(revision 5624)
@@ -523,4 +523,5 @@
   g_config->streams[i].mixer.scale    = 65535;
   g_config->streams[i].mixer.mixer[0] = g_config->streams[i].mixer.scale;
+  g_config->streams[i].rpgmode        = ROAR_RPGMODE_ALBUMTRACK;
  }
 
Index: roard/streams.c
===================================================================
--- roard/streams.c	(revision 5415)
+++ roard/streams.c	(revision 5624)
@@ -234,4 +234,6 @@
    s->mixerstream =  NULL;
 #endif
+
+   s->rpgmode = ROAR_RPGMODE_DEFAULT;
 
    //roardsp_fchain_init(&(s->fc));
@@ -1508,4 +1510,58 @@
 }
 
+int streams_get_rpg      (int id, struct roar_stream_rpg * rpg) {
+ _CHECK_SID(id);
+
+ if ( rpg == NULL ) {
+  roar_err_set(ROAR_ERROR_FAULT);
+  return -1;
+ }
+
+ rpg->mode = g_streams[id]->rpgmode;
+ rpg->mul  = g_streams[id]->mixer.rpg_mul;
+ rpg->div  = g_streams[id]->mixer.rpg_div;
+
+ if ( rpg->mode == ROAR_RPGMODE_DEFAULT )
+  rpg->mode = g_config->streams[ROAR_STREAM(g_streams[id])->dir].rpgmode;
+
+ return 0;
+}
+
+int streams_set_rpg      (int id, const struct roar_stream_rpg * rpg) {
+ int mode;
+
+ _CHECK_SID(id);
+
+ if ( rpg == NULL ) {
+  roar_err_set(ROAR_ERROR_FAULT);
+  return -1;
+ }
+
+ // test if we have a valid value at all.
+ if ( rpg->mode > ROAR_RPGMODE_TRACKALBUM || rpg->mode < ROAR_RPGMODE_DEFAULT ) {
+  roar_err_set(ROAR_ERROR_RANGE);
+  return -1;
+ }
+
+ mode = g_streams[id]->rpgmode = rpg->mode;
+
+ if ( mode == ROAR_RPGMODE_DEFAULT )
+  mode = g_config->streams[ROAR_STREAM(g_streams[id])->dir].rpgmode;
+
+ switch (mode) {
+  case ROAR_RPGMODE_NONE:
+    g_streams[id]->mixer.rpg_mul = 1;
+    g_streams[id]->mixer.rpg_div = 1;
+   break;
+  case ROAR_RPGMODE_USER:
+    g_streams[id]->mixer.rpg_mul = rpg->mul;
+    g_streams[id]->mixer.rpg_div = rpg->div;
+   break;
+ }
+
+ return 0;
+}
+
+
 int streams_ltm_ctl      (int id, int mt, int window, int cmd) {
  struct roar_stream_server * ss;
