tags:

views:

686

answers:

4

Hi,

I'm trying to process my byte array which I got from the sampled sourcedataline (Java Sound API). If I'm multiplying the byte array with a fraction number, I will get noise while playing the stream.

Before I'm playing the sound I separate the stereo wav file into his left and right channel. This works fine. But if I want to process the channels with a gain control, which depends on a delay factor, I get noise.

for(int i=0; i<bufferSize; i++) { array[i] = (byte) (array[i] * gain); }

Does anyone know how to fix the problem?

//EDIT:

I tried to convert the two bytes into a short (2bytes) with bit shifting e.g.:

short leftMask = 0xff00;
short rightMask = 0x00ff;
short sValue = (array[i] + array[i+1] <<8) * gain;

array[i] = (sValue & leftMask) >> 8;
array[i+1] = (sValue & rightMask);

but I got the same when I just multiply the single bytes with the gain value.

//EDIT

OR should I just add the two array values into a short like this?

short shortValue = array[i] + array[i+1];
shortValue *= gain;
array[i] = ???

But how do I convert this short into the 2 single bytes without losing the sound?

//EDIT some code from the separating method:

public static void channelManipulation(byte[] arrayComplete) {
     int i=2; 
     char channel='L';
     int j=0; 

     /** 
      * The stereo stream will be divided into his channels - the Left and the Right channel. 
      * Every 2 bytes the channel switches. 
      * While data is collected for the left channel the right channel will be set by 0. Vice versa.
      */
     while(j<arrayComplete.length) {
      //while we are in the left channel we are collecting 2 bytes into the arrayLeft


      while(channel=='L') {
       if(i==0) {
        channel='R'; //switching to the right channel
        i=2;
        break;
       }
       arrayLeft[j] = (byte)(arrayComplete[j]);
       arrayRight[j] = 0;
       i--; j++;
      }

      //while we are in the right channel we are collecting 2 bytes into the arrayRight
      while(channel=='R') {
       if(i==0) {
        channel='L'; //switching to the left channel
        i=2;
        break;
       }
       arrayRight[j] = (byte) (arrayComplete[j]);
       arrayLeft[j] = 0;
       i--; j++;
      }
     }

    }
+1  A: 

You need to apply some clipping. Suppose you have a sample of value 100, and you are applying a gain of 2. The result of the multiplication will be 200, which then ends up truncated to -73.

Try:

array[i] = Math.min(Math.max(array[i] * gain, -128), 127);

As a test for this - if you apply a gain which is effectively a "quietening" gain (e.g. 0.5) you shouldn't get noise at the moment.

EDIT: If the "raw" values aren't actually single bytes, you should convert from the byte array to those values first, and then apply the gain, then convert back to single bytes. Otherwise you will indeed get some strange results... particularly if the native format is actually treating the bytes as unsigned values...

Jon Skeet
I tried to set the gain to e.g. 0.5 and i got also the noise...
sn3ek
Editing answer...
Jon Skeet
ya and thats exactly what i was done. An example how i tried to convert the single byte into a short array:short maskLeft = 0xff00;short maskRight = 0x00ff;short shortLeft = array[i] + array[i]>>8;shortLeft *= gain;array[i] = (shortLeftarray[i+1] = shortLeft
sn3ek
sorry but i don't know how to set code in this comment field... i will edit my question
sn3ek
+1  A: 

Does not work. I have this code snippet:

for(int c=0; c<Constants.getBufferlength()-4; c+=4) {
      arrayLeft[c] = (byte) Math.min(Math.max((arrayLeft[c]*leftGain), -128), 127);

      arrayRight[c] = (byte) Math.min(Math.max((arrayRight[c]*rightGain),-128),127);

 }

I got the noise like before.

sn3ek
Please don't reply in answers. Edit your question instead. However, why are you ignoring 3/4 of your values?
Jon Skeet
oh that was why I edited my code. the code was: for(int c=0; c<Constants.getBufferlength()-4; c+=4) { arrayLeft[c] = (byte) Math.min(Math.max((arrayLeft[c]*leftGain), -128), 127); arrayLeft[c+1] = (byte) Math.min(Math.max((arrayLeft[c+1]*leftGain), -128), 127); arrayRight[c+2] = (byte) Math.min(Math.max((arrayRight[c+2]*rightGain),-128),127); arrayRight[c+3] = (byte) Math.min(Math.max((arrayRight[c+3]*rightGain), -128), 127); }
sn3ek
+3  A: 

Even though your audio data is in the form of a byte array, your real audio is (I'm assuming) an array of short (2-byte) integers. When you multiply each individual byte of your array by a gain factor, you're turning the 2-byte sample values into gibberish (aka noise). I'm not a java programmer, but your solution is to cast the byte array as a 2-byte integer array (however you do that in java), and then multiply each 2-byte integer value by the gain factor (and then, I guess, cast it back to a byte array before playback).

Update: in C#, if I had a byte array of audio data (say, pulled out of a WAV file where the real format was 2-byte integer), I would apply the gain using the BitConverter and Array classes like this:

float gain = 0.5f;
for (int i = 0; i < audio.Length; i += 2)
{
    short val = BitConverter.ToInt16(audio, i);
    val = (short)((float)val * gain);
    Array.Copy(BitConverter.GetBytes(val), 0, audio, i, 2);
}

This is pretty clunky, and it's not something I would ever really do. In the C# world, I always work with audio as an array of 16- or 32-bit integers, or else as 32- or 64-bit floating point values. I really don't know how java audio works at all, but it should be possible (and much easier) somewhere to get your audio as an array of 16-bit integers in the first place - then you won't have to do any weird conversions like this to apply gain or do whatever else you want to do.

Update 2: also, I'm not sure your original audio source actually consists of 2-byte integer sample values. It may in fact be 4-byte integer or (more likely) 4-byte floating-point sample values, in which case my sample code would still produce noise. With 4-byte float, the proper code would be:

float gain = 0.5f;
for (int i = 0; i < audio.Length; i += 4)
{
    float val = BitConverter.ToSingle(audio, i);
    val *= gain;
    Array.Copy(BitConverter.GetBytes(val), 0, audio, i, 4);
}
MusiGenesis
Do you have some code snippet in your preferred language? Because I tried this with bit shifting but its the same result like the for loop.
sn3ek
@MusiGenesis: Yup, that's exactly what I've just been thinking too.
Jon Skeet
@sn3ek: your bit-shifting code may have worked correctly, but it would still produce noise if the real audio format was 4-byte float instead of 2-byte integer.
MusiGenesis
You are right - it is a 4Byte stream. Because after separating the channels I got LL00 and 00RR as 4 Bytes. But I don't know how to fix my problem. I'm working since 1 week at this problem but does not know how to fix.
sn3ek
@sn3ek: could you clarify what you mean by "after separating the channels I got LL00 and 00RR as 4 Bytes"? If your format is 16-byte integer samples in stereo, then each block of 4 bytes will represent *two* sample values (one for the left channel and one for the right).
MusiGenesis
@MusiGenesis I have added the code from my method. It's not elegant but it works. If I'm playing just the left array I will listen only to the left channel. And also you can see that I have a 4 Byte Stream. 2Bytes for the left channel data and 2 bytes for the right channel data. Thats what I mean with LL00 and 00RR. If I have the left array I have 2 bytes with data (LL) and 2 Bytes with no data (00) and vice versa.
sn3ek
OK, you *are* working with the WAV format then (stereo, 2-bytes-per-sample). My first code sample should work fine for you, if you can translate it into java. For that code, it doesn't matter whether the raw data is stereo or mono, since you're just attenuating every sample value regardless of channel. If I were you, I would just ask another StackOverflow question about how to convert a byte array into a 2-byte integer array. Make sure you specify that each pair of bytes in the original should be converted into a single short integer value in the output.
MusiGenesis
And you can thank me by voting up this answer (me wants another gold badge): http://stackoverflow.com/questions/297037/what-tricks-do-you-use-to-get-yourself-in-the-zone/297090#297090
MusiGenesis
If I will have 15 points I can vote up - but I can't because I have only 9points. But if I will have 15 points and more I will vote you up!
sn3ek
You're good - go for it (I'm so ashamed). :)
MusiGenesis
A: 

I found this post after having a very similar sounding problem. FWIW my problem was solved by noting that code such as

short sValue = (array[i] + array[i+1] <<8)

doesn't account for the effect of Java's signed bytes. If the high bit is set in the low byte (e.g. array[i]), then this would have no effect for a short but does effect code that does arithmetic with the short's two bytes separately. Easily fixed by code along the lines of

if(array[i] < 0)
    array[i+1] += 1;

which adds 256 to the short - accounting for the missing 128 bit in the low byte and the two's complement of the rest. You might need some variation on this depending on how you're processing the byte array.

thekindamzkyoulike