Author Topic: Normalize output sound?!  (Read 29774 times)

Led

  • Posts: 40
Re: Normalize output sound?!
« Reply #25 on: 1 Jul '02 - 16:54 »
I think for a lot of people here it would be worth it to learn how to use a debugger :)

This looks like a simple array-index-out-of-range problem or using memory that's already been freed or one of the other standard errors :)

just step through it in a debugger and you would probably have found the bug faster than you could post it on a forum ;-)

Largie

  • Posts: 110
Re: Normalize output sound?!
« Reply #26 on: 1 Jul '02 - 17:34 »
Quote

This looks like a simple array-index-out-of-range problem or using memory that's already been freed or one of the other standard errors :)
just step through it in a debugger and you would probably have found the bug faster than you could post it on a forum ;-)


As Jobnik does not release his DLL with debug info, the debugger gives asm code back when crashing! So find out what kind of error that have happend is Jobnik's problem!  :evil:

I have just experienced the same trouble in the BASS dll, no info out of the access violation message or which line the code in bass failed etc...

People need the source for that...

midijor

  • Posts: 10
Re:Normalize output sound?!
« Reply #27 on: 20 Feb '04 - 18:12 »
Hi;

I need the code for Delphi 6.

Is it possible?

Best regards.

leighharrison

  • Posts: 6
Re:Normalize output sound?!
« Reply #28 on: 28 Feb '04 - 04:06 »
I've trawled this thread a few times looking for some solutions to an issue with the audio software I've written for my own use.

MusicMaid has a cue thread that operates on queued tracks before they're played. It inches from the start and end of tracks to find the point at which audio bumps above or drops below the configurable threshold. It then cycles back from the end to find the point at which the audio drops below the configurable crossfade threshold.  This produces a pleasingly seamless listening environment ... mostly.

The problem is that about 30% of my classical tracks (and a fair number of others) peak a lot lower than 100%. Worst case, I have Chopin nocturnes that crossfade neatly into the next track as soon as they start! Bother, Frederick!

I now have a workable solution, and it may be of interest. It's only approximately accurate, but it works - so far every time - and it takes only an additional fifteen seconds processing on the cueing thread.

I'll describe it first, and then post some code I've pulled out of MusicMaid to illustrate the approach. It's written in Delphi, but I could port it to c++ or c# if that would be useful.

OK.

I skim the song, taking a volume sample every, say, five seconds. I store the level and the position of each. Then I sort by level and pull out the positions of several of the highest ones (configurable, but more on that in a moment). I then repeat the process either side of these highest level points, but sampling at one second instead of five. I then sort these results in the same way, go back and take very close-grained samples adjacent to the highest of them.

What I end up with is an (approximate) max volume value. I then divide that into the absolute maximum volume to return a ratio by which the levels should be multiplied to peak at 100%. This is, please, acknowledged to be approximate. In my player, I have a configurable "normal" replay volume - it's a percentage, because that's how Bass lets me set it on a per-channel basis. I multiply this by the ratio I've calculated, and truncate it to 100%. It's solved my crossfade issue.

I tested this pulling out a different number of results (the cPeakCount constant). Setting this to 1 costs 4-6 seconds per song but isn't really very accurate. Setting it to 5 costs 22-26 seconds and is pretty damn good. I've settled on 3, which is almost always the same as 5, but takes only 14-16 seconds.

Here are the code bits for those interested. I know one of the tags lets me present this in a nice "code" format, but there are no hints on the buttons and I have a new skirt to hem before I go out tonight, so tough.

include
  Bass, Math;

const
  cPeakCount = 3;
  cBaseLevel = 33;

function NormaliseMePleaseMiss(const sFileName: String): Double;
var
  arrBroad,
  arrFine: array of Int64;
  Stream: HStream;
begin
  Stream := BASS_StreamCreateFile(
      False, PChar(sFileName), 0, 0, BASS_MP3_SETPOS);

  try
    {Turn the volume off}
    BASS_ChannelSetAttributes(Stream, -1, 0, -101);

    {Get the likely loudest sections in the song.}
    arrFine := nil;
    SetLength(arrBroad, cPeakCount);
    GetLoudestPositions(Stream, 0, 5, arrFine, arrBroad);

    {Get the three loudest points across these sections.}
    SetLength(arrFine, cPeakCount);
    GetLoudestPositions(Stream, 5, 1, arrBroad, arrFine);

    {Get the loudest volume adjacent to these points and return the
    degree of change necessary to adjust this to 100%.}
    Result := GetHighestLevelFactor(Stream, 2, arrFine);
  finally
    BASS_StreamFree(FStream);
  end;
end;

function GetInterval(Stream: HStream; iSeconds: Integer): Integer;
var
  dwFreq,
  dwVol: DWord;
  iPan: Integer;
begin
  BASS_ChannelGetAttributes(Stream, dwFreq, dwVol, iPan);

  if (Bass_ChannelGetFlags(Stream) and BASS_SAMPLE_MONO) = 0 then
    dwFreq := dwFreq * 2;

  if (Bass_ChannelGetFlags(Stream) and BASS_SAMPLE_8BITS) = 0 then
    dwFreq := dwFreq * 2;

  if dwFreq > 0 then
    Result := iSeconds * dwFreq
  else
    Result := -1;
end;

procedure GetLoudestPositions(Stream: HStream;
  iOffset, iInterval: Integer; var arrIn, arrOut: array of Int64);
var
  iPos,
  iStepBy: Integer;
  listValues: TStringlist;

  {Internal method: I don't like these, but I have bran muffins to make
  this afternoon and this is quick.}
  procedure ListLevels(iStart, iEnd: Int64);
  begin
    while iStart < iEnd do begin
      BASS_ChannelSetPosition(Stream, iStart);
      BASS_StreamPlay(Stream, False, 0);
      listValues.Add(Format('%s=%d', [
        FormatFloat('00000000', BASS_ChannelGetLevel(Stream)), iStart]));
      BASS_ChannelStop(FStream);
      Inc(iStart, iStepBy);
    end;
  end;

begin
  iStepBy := GetInterval(Stream, iInterval);
  listValues := TStringlist.Create;

  try
    if Length(arrIn) = 0 then
      ListLevels(0, BASS_StreamGetLength(Stream))
    else for iPos := Low(arrIn) to High(arrIn) do
      ListLevels(
        arrIn[iPos] - GetInterval(Stream, iOffset),
        arrIn[iPos] + GetInterval(Stream, iOffset));

    {If we don't have at least the number of return values requested,
    return empty.}
    if listValues.Count < Length(arrOut) then
      Exit;

    listValues.Sort;

    for iPos := Low(arrOut) to High(arrOut) do
      arrOut[iPos] :=
        StrToIntDef(listValues.Names[listValues.Count - (iPos + 1)], -1);
  finally
    listValues.Free;
  end;
end;

function GetHighestLevelFactor(Stream: HStream; iOffset: Integer;
  arrIn: array of Int64): Double;
var
  iPos: Integer;
  iStart,
  iEnd: Int64;
  dwHighest: DWord;
begin
  dwHighest := 0;

  for iPos := Low(arrIn) to High(arrIn) do begin
    iStart := arrIn[iPos] - GetInterval(Stream, iOffset);
    iEnd := arrIn[iPos] + GetInterval(Stream, iOffset);
    BASS_ChannelSetPosition(Stream, iStart);
    BASS_StreamPlay(Stream, False, 0);

    while iStart <= iEnd do begin
      Application.ProcessMessages;
      dwHighest := MaxIntValue([dwHighest, BASS_ChannelGetLevel(Stream)]);
      iStart := BASS_ChannelGetPosition(Stream);
    end;
  end;

  Result := MakeLong(128, 128) / dwHighest;
end;

procedure WillYouShutUpAndPlayTheSongMadam(const sFileName: String; dFactor: Double);
var
  iLevel: Integer;
  Stream: HStream;
begin
  Stream := BASS_StreamCreateFile(
      False, PChar(sFileName), 0, 0, BASS_MP3_SETPOS);
  iLevel := MinIntValue([Round(cBaseLevel * FLevel), 100]);
  BASS_ChannelSetAttributes(Stream, -1, iLevel, -101);
  BASS_StreamPlay(Stream, False, 0);
end;


veetid

  • Guest
Re: Normalize output sound?!
« Reply #29 on: 27 Jun '05 - 07:12 »
I am attempting to convert this normalization code to work in csharp using the interop.  Here is what I have so far and it compiles and runs.  I don't really have any files to test to see if it works.

private AUTOAMPSTUFF amp;
      private Un4seen.BassLib21.Bass.DSPPROC_Handler normcallback;
      private float amptarget = 30000;
      private float ampquiet = 800;
      private float amprate = 0.02f;

      private struct AUTOAMPSTUFF
      {
         public float gain; // amplification level
         public int delay; // delay before increasing level
         public int count; // count of sequential samples below target level
         public int high; // the highest in that period
         public int quiet; // count of sequential samples below quiet level
      }

public void SetupNormalize()
      {
         amp.gain = 1;
         amp.delay = ((int)(Un4seen.BassLib21.Bass.BASS_ChannelSeconds2Bytes(strm, 1) / 2));
         amp.count = amp.high = amp.quiet = 0;

         normcallback = new Un4seen.BassLib21.Bass.DSPPROC_Handler(autoamp);
         Un4seen.BassLib21.Bass.BASS_ChannelSetDSP(strm, normcallback, 1, 1);
      }

      private void autoamp(int handle, int channel, IntPtr buffer, int length, int camp)
      {
         try
         {
            short[] data = new short[length/2];
            Win32.CopyMemory(data, buffer, length);

            for(int c=0;c<length/2;c++)
            {
               int s = (int)(data[c]*amp.gain); // amplify sample
               int sa = Math.Abs(s);

               if(Math.Abs(data[c]) < ampquiet) // sample is below quiet level
                  amp.quiet++;
               else
                  amp.quiet = 0;
            
               if(sa < amptarget) // amplified level is below target
               {
                  if(sa > amp.high) amp.high = sa;
               
                  amp.count++;
               
                  if(amp.count == amp.delay) // been below target for a while
                  {
                     if(amp.quiet > amp.delay) // it's quiet, go back towards normal level
                        amp.gain += 10*amprate*(1-amp.gain);
                     else
                        amp.gain += amprate*amptarget/amp.high; // increase amp

                     amp.high = amp.count = 0; // reset counts
                  }
               }
               else // amplified level is above target
               {
                  if(s<-32768) s=-32768;
                  else if (s>32767) s=32767;
                  amp.gain -= 2 * amprate * sa/amptarget; // decrease amp
                  amp.high = amp.count = 0;
               }

               data[c] = (short)s; // replace original sample with amplified version
            }

            Win32.CopyMemory(buffer, data, length);
         }
         catch {}
      }


If someone trys it out, let me know if it works..

edsonBR

  • Posts: 3
Re: Normalize output sound?!
« Reply #30 on: 19 Oct '08 - 06:07 »
Someone could translate the code for Delphi? Please!

Bye!

Nicolás

  • Guest
Re: Normalize output sound?!
« Reply #31 on: 27 Oct '08 - 18:44 »


I need to normalize, some body have the example in VB language???

Realy thanks!

serkanp

  • Posts: 64
Re: Normalize output sound?!
« Reply #32 on: 7 Nov '13 - 15:30 »
can someone convert this code to c# :))
there are pointers etc in here.. i can't get it fully converted and working...


Here's a simple dynamic amplification DSP...
Code: [Select]
#define amptarget 30000 // target level
#define ampquiet 800 // quiet level
#define amprate 0.02f // amp adjustment rate

typedef struct {
      float gain; // amplification level
      int delay; // delay before increasing level
      int count; // count of sequential samples below target level
      int high; // the highest in that period
      int quiet; // count of sequential samples below quiet level
} AUTOAMPSTUFF;

void CALLBACK autoamp(HDSP handle, DWORD channel, void *buffer, DWORD length, AUTOAMPSTUFF *amp)
{
      short *data=(short*)buffer;
      DWORD c;
      for (c=0;c<length/2;c++) {
            int s=(int)(data[c]*amp->gain); // amplify sample
            int sa=abs(s);
            if (abs(data[c])<ampquiet)
                  amp->quiet++; // sample is below quiet level
            else
                  amp->quiet=0;
            if (sa<amptarget) { // amplified level is below target
                  if (sa>amp->high) amp->high=sa;
                  amp->count++;
                  if (amp->count==amp->delay) { // been below target for a while
                        if (amp->quiet>amp->delay)
                              // it's quiet, go back towards normal level
                              amp->gain+=10*amprate*(1-amp->gain);
                        else
                              amp->gain+=amprate*amptarget/amp->high; // increase amp
                        amp->high=amp->count=0; // reset counts
                  }
            } else { // amplified level is above target
                  if (s<-32768) s=-32768;
                  else if (s>32767) s=32767;
                  amp->gain-=2*amprate*sa/amptarget; // decrease amp
                  amp->high=amp->count=0;
            }
            data[c]=(short)s; // replace original sample with amplified version
      }
}

...

AUTOAMPSTUFF amp;

amp.gain=1;
amp.delay=BASS_ChannelSeconds2Bytes(handle,1)/2;
amp.count=amp.high=amp.quiet=0;
BASS_ChannelSetDSP(handle,(DSPPROC*)&autoamp,&amp);

LilacPro001

  • Posts: 7
Re: Normalize output sound?!
« Reply #33 on: 15 Jan '15 - 09:24 »
Would it be possible to translate the normalization code to VB6 Please? Im not much of a C developer... :-)

Ian @ un4seen

  • Administrator
  • Posts: 20389
Re: Normalize output sound?!
« Reply #34 on: 16 Jan '15 - 16:32 »
The BASS_FX add-on's BASS_FX_BFX_DAMP effect is based on the code in this thread, so you could use that (via BASS_ChannelSetFX) instead.