Author Topic: Bass.Net - Load Mod -> lower global volume > normalize -6DB > save to wav  (Read 90 times)

Wroah

  • Posts: 2
I'm a C# programmer, but not really good at math and binary. I just found out about Bass, and I think I could accomplish what I want using Bass.net. However, I'm a bit clueless; how should I chain this together? Which api methods do I need?

Basically I want to do the following:

1. Open Module files in high quality (48000)
2. Lower volume (so that it won't peak directly)
3. Normalize to -6 DB
4. Save to 24bit 48000 wav file
5. Process it further (master it a little in an external program)

So I got step one (and step 2, maybe)

Code: [Select]
public static void PlayAMod()
{
   BassNet.Registration("x", "y");

   var success = Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
   var i = Bass.BASS_MusicLoad(@"C:\Users\Wroah\Desktop\X\E-ACDRMX.IT", 0, 0, BASSFlag.BASS_MUSIC_LOOP, 0);

   Bass.BASS_ChannelPlay(i, false);
   Bass.BASS_SetVolume(.05f);

   Console.ReadLine();
   Console.WriteLine(success);
}

I found out about the wavewriter, but don't know how to "feed" it

http://bass.radio42.com/help/html/50c9884f-89b0-5fc7-dc36-3b5d97543195.htm

Here is "Utils.GetNormalizationGain", a method I probably need:
http://bass.radio42.com/help/html/e208d868-733c-bc97-0950-d3c8cb266723.htm

And here some info about the normalization process, but I need to have the peak at -6db, and work with 24bit, not 16bit:
https://www.un4seen.com/forum/?topic=6466.0

I'd really appreciate some help. I get it if you don't want to spell it all out for me, but some hints at least would be nice, especially for the normalization part
« Last Edit: 4 Jun '21 - 20:50 by Wroah »

radio42

  • Posts: 4717
What does your step 3 mean "Normalize to -6 DB" - you are not talking about ReplayGain here, but a simple peak level normalization?
Note, that peak level normalization is always s 2 pass process, i.e. you first need to read all samples (decoding the mod for the first time) in order to find the absolute peak level value.
And then to a simple calculation to attenuate resp. increase all samples data, so that the found peak level now matches -6 dB.

And what should step 5 mean "master it a little".

So BASS/Bass.Net will help you in decoding the mod files and get you all sample data (BASS_ChannelGetData; which returns the sample data e.g. in Int16 or float values).
It can modify the sample data retrieved - just apply your own math (step 2, 3 or 5).
And you can save the modified sample data data to a Wav file (e.g. using the BASSenc add-on, see the BASS_Encode_Start method using the BASS_ENCODE_PCM flag).

But the math, i.e. the normalization, lowering the volume (value = value * 0,x) or the more complex processing/mastering need to be done by yourself - or you apply e.g. a VST on the file (see the BASS_VST add-on).

Ian @ un4seen

  • Administrator
  • Posts: 23619
If you won't be playing anything then you could use the "No Sound" device (device=0 in the BASS_Init call). You should also include the BASS_MUSIC_DECODE flag in the BASS_MusicLoad call. For normalization, you will need 2 passes: first to just scan the normal peak level, and then the file writing with a gain adjustment based on that level. You can use BASS_ChannelGetLevelEx for the peak scanning, and the BASS_FX_VOLUME effect via BASS_ChannelSetFX for the the gain adjustment. You can use the BASSenc add-on for the file writing.

Note that BASS_ChannelGetLevelEx currently has a 1 second scan limit, meaning you'll need multiple calls to scan the entire file, but that limit is being removed in the next BASS release. You can get the latest build including that change here:

   www.un4seen.com/stuff/bass.zip

I'm not a .Net user myself, but the code should be much the same in any language, and it could look something like this in C/C++:

Code: [Select]
BASS_Init(0, 48000, 0, 0, 0); // initialize "no sound" device

DWORD music = BASS_MusicLoad(false, filename, 0, 0, BASS_MUSIC_DECODE | BASS_SAMPLE_FLOAT, 48000); // load MOD file

float level[1];
BASS_ChannelGetLevelEx(music, level, 600, BASS_LEVEL_MONO); // scan up to 10 minutes for peak level
HFX volfx = BASS_ChannelSetFX(music, BASS_FX_VOLUME, 0); // set a volume effect on the MOD
BASS_FX_VOLUME_PARAM volparam = {
0.5 / level[0], // the volume adjustment (0.5 = -6dB target)
0,
0,
0
};
BASS_FXSetParamterts(volfx, volparam); // set the effect parameters

BASS_Encode_Start(music, "output.wav", BASS_ENCODE_PCM | BASS_ENCODE_FP_24BIT | BASS_ENCODE_AUTOFREE, 0, 0, 0); // set a 24-bit WAV writer on the MOD
BASS_ChannelSetPosition(music, 0, BASS_POS_BYTE | BASS_MUSIC_POSRESETEX); // rewind/reset the MOD
while (1) { // processing loop
BYTE buf[20000];
if (BASS_ChannelGetData(music, buf, sizeof(buf)) == -1)
break; // done/error
}

BASS_MusicFree(music); // free the MOD (and WAV writer due to AUTOFREE)

Please see the documentation for details on the mentioned functions. Note that some MOD files may never end, so you may want to limit the processing loop, eg. change it to a "for" loop.

Wroah

  • Posts: 2
Sorry for the long delay, I am a weekend warrior when it comes to coding, have two kids and life happens, so I didn't have time until today.

Ian, I take my hat off, thank you so much! I would have never figured it out on my own. Plus, I was running in some problems with dll-hell (turned out I had a 64 bit bass.dll being called by a 32 bit bass.net wrapper and a 32 bit basenc.dll, plus some problems with dll paths (wrapper couldn't find bass.dll). I tested with both the old bass.dll and the new one. The new one works as you mentioned, plus, I noticed a huge speed difference in exporting to wave. Here is my C# version using bass.net, for people looking to do the same thing:

Code: [Select]
public static void ExportToWave(string sourePath, string targetPath)
{
Bass.BASS_Init(0, 48000, 0, IntPtr.Zero);

var musicPtr = Bass.BASS_MusicLoad(sourePath, 0, 0, BASSFlag.BASS_MUSIC_DECODE | BASSFlag.BASS_SAMPLE_FLOAT, 48000);

float[] level = new float[1];
Bass.BASS_ChannelGetLevel(musicPtr, level, 600, BASSLevel.BASS_LEVEL_MONO);

var volfx = Bass.BASS_ChannelSetFX(musicPtr, BASSFXType.BASS_FX_VOLUME, 0);
var volParam = new BASS_FX_VOLUME_PARAM(0.5f / level[0], 0, 0, 0);

Bass.BASS_FXSetParameters(volfx, volParam);

BassEnc.BASS_Encode_Start(musicPtr, targetPath, BASSEncode.BASS_ENCODE_PCM | BASSEncode.BASS_ENCODE_FP_24BIT | BASSEncode.BASS_ENCODE_AUTOFREE, null, IntPtr.Zero, 0);
Bass.BASS_ChannelSetPosition(musicPtr, 0, BASSMode.BASS_POS_BYTE | BASSMode.BASS_MUSIC_POSRESETEX);

for (var i = 0; i < Int16.MaxValue; i++) //TODO
{
Byte[] buf = new byte[20000];
if (Bass.BASS_ChannelGetData(musicPtr, buf, buf.Length) == -1)
break; //finished, or exception? TODO
}

Bass.BASS_MusicFree(musicPtr);
}

One thing I don't understand in the code.... Just asking out of curiosity.

In the while loop, You assign this byte array of 20000, and "give" it to the function BASS_ChannelGetData. I mean, I don't completely understand what this function does, although I red in the manual that it "Retrieves the immediate sample data (or an FFT representation of it) of a sample channel, stream, MOD music, or recording channel." I guess the actual manipulation (normalize) is happening in this function, while it retrieves sample data.

But, why do I need to do it this way? I don't understand why I pass a "buffer" but do nothing with it. I assume there is no function or overload that just processes all sample data / the whole song? Or does this has to do with the looping of mods or memory issues? And why this number 20000?
« Last Edit: 12 Jun '21 - 20:07 by Wroah »