/// <summary> /// Converts 24-bit samples to the specified format, using the specified /// buffer. Each <see langword="int"/> in the specified buffer will be /// interpreted as 24-bit values, and any extra bits will be ignored. /// If <paramref name="target"/> is equal to <see cref="AudioBitDepth.Bits24"/>, /// no action is performed. /// </summary> protected static unsafe void ChangeBitDepth24(byte *outputPtr, AudioBitDepth target, byte channels, int *inputPtr) { switch (target) { case AudioBitDepth.Bits8: { // 24bit -> 8bit for (int j = 0; j < channels; j++) { *(outputPtr + j) = (byte)(*(inputPtr + j) >> 16); } break; } case AudioBitDepth.Bits16: { // 24bit -> 16bit short *opBufSamplePtr = (short *)outputPtr; for (int j = 0; j < channels; j++) { *(opBufSamplePtr + j) = (short)(*(inputPtr + j) >> 8); } break; } case AudioBitDepth.Bits32: { // 24bit -> 32bit int *opBufSamplePtr = (int *)outputPtr; for (int j = 0; j < channels; j++) { *(opBufSamplePtr + j) = *(inputPtr + j) << 8; } break; } } }
/// <summary> /// Initializes a new instance of the <see cref="SampleFormat"/> structure. /// </summary> /// <param name="bitDepth">The bit-depth (bits per a single sample in a channel).</param> /// <param name="channels">The amount of channels.</param> /// <param name="signed">Whether the audio is signed, that is, whether the sample can store negative values. By convention, only 8-bit audio samples are unsigned.</param> public SampleFormat(AudioBitDepth bitDepth, byte channels, bool signed) { BitDepth = bitDepth; Channels = channels; Signed = signed; Size = ChannelSize * channels; }
/// <summary> /// Converts 16-bit samples to the specified format, using the specified /// buffer. If <paramref name="target"/> is equal to <see cref="AudioBitDepth.Bits16"/>, /// no action is performed. /// </summary> protected static unsafe void ChangeBitDepth(byte *outputPtr, AudioBitDepth target, byte channels, short *inputPtr) { switch (target) { case AudioBitDepth.Bits8: { // 16bit -> 8bit for (int j = 0; j < channels; j++) { *(outputPtr + j) = (byte)(*(inputPtr + j) >> 8); } break; } case AudioBitDepth.Bits24: { // 16bit -> 24bit for (int j = 0; j < channels; j++) { WriteInt24ToBuffer(outputPtr, j * 3, *(inputPtr + j) << 8); } break; } case AudioBitDepth.Bits32: { // 16bit -> 32bit int *opBufSamplePtr = (int *)outputPtr; for (int j = 0; j < channels; j++) { *(opBufSamplePtr + j) = *(inputPtr + j) << 16; } break; } } }
/// <summary> /// Reads a single channel from the specified sample in the specified format /// and returns a pointer to the first byte of the read sample. /// </summary> private unsafe byte *ReadChannel(int index, int channel, bool signed, AudioBitDepth bitDepth) { target.ReadSampleChannel(index, channel, buffer); fixed(byte *bufferPtr = buffer) { EnsureBufferFormat(bufferPtr, bitDepth, signed, 1); return(bufferPtr); } }
/// <summary> /// Makes a single unsigned audio sample signed. /// </summary> /// <param name="sourcePtr">The pointer to the target sample.</param> /// <param name="format">The audio format of the sample.</param> protected unsafe static void MakeSigned(byte *sourcePtr, AudioBitDepth bitDepth, byte channels) { // To change the sign of an unsigned value: value - ((data type max value / 2) + 1) switch (bitDepth) { case AudioBitDepth.Bits8: for (int j = 0; j < channels; j++) { byte rawSample = sourcePtr[j]; // Unsigned 8-bit -> Signed 8-bit sbyte sample = (sbyte)(rawSample - 128); rawSample = unchecked ((byte)sample); sourcePtr[j] = rawSample; } break; case AudioBitDepth.Bits16: for (int j = 0; j < channels; j++) { // Unsigned 16-bit -> Signed 16-bit ushort origSample = *((ushort *)sourcePtr + j); short sample = (short)(origSample - 32768); *((short *)sourcePtr + j) = sample; } break; case AudioBitDepth.Bits24: for (int j = 0; j < channels; j++) { // Unsigned 24-bit -> Signed 24-bit int byteAlignment = j * 3; int origSample = ToInt24(sourcePtr, byteAlignment); int sample = origSample - 8388608; WriteInt24ToBuffer(sourcePtr, byteAlignment, sample); } break; case AudioBitDepth.Bits32: for (int j = 0; j < channels; j++) { // Unsigned 32-bit -> Signed 32-bit uint origSample = *((uint *)sourcePtr + j); int sample = (int)(origSample - 2147483648); *((int *)sourcePtr + j) = sample; } break; } }
private unsafe void EnsureBufferFormat(byte *bufferPtr, AudioBitDepth bitDepth, bool signed, byte channels) { if (target.Format.BitDepth != bitDepth) { ChangeBitDepth( bufferPtr, channels, target.Format.BitDepth, bitDepth ); } if (target.Format.Signed && !signed) { MakeUnsigned(bufferPtr, bitDepth, channels); } else if (!target.Format.Signed && signed) { MakeSigned(bufferPtr, bitDepth, channels); } }
/// <summary> /// Converts the specified samples to the specified target format. /// The specified buffer pointer must be large enough to hold both /// the input samples, and the converted output samples. /// </summary> protected static unsafe void ChangeBitDepth(byte *bufferPtr, byte channels, AudioBitDepth inputBitDepth, AudioBitDepth outputBitDepth) { // Convert the bit-depth // We have to re-normalize the samples; from the source data type's min and max value, // to the target data type's min and max values - this operation can be represented by // the following formula: // (value / max source data type value) * max target data type value // For example, to convert a 16-bit sample 0x0E to a 32-bit sample: // (14 / 32767) * 2147483647 // However, this approach, while the most obvious from a mathematical standpoint, // uses floating-point math. This can be much slower than direct bit-manipulation. // The code below will do the equivalent to the provided formula by using // bit-manipulation operators, making the method much more performant. // // By using bit-shifting, we do not have to change the sign of the samples // which is useful for us as we convert the samples in several different passes. switch (inputBitDepth) { case AudioBitDepth.Bits8: { // 8 bit -> target bit depth byte[] samples = new byte[channels]; fixed(byte *samplesPtr = samples) { MemoryOperations.Copy(samplesPtr, bufferPtr, channels); ChangeBitDepth(bufferPtr, outputBitDepth, channels, samplesPtr); } break; } case AudioBitDepth.Bits16: { // 16 bit -> target bit depth short[] samples = new short[channels]; fixed(short *samplesPtr = samples) { MemoryOperations.Copy((byte *)samplesPtr, bufferPtr, channels * sizeof(short)); ChangeBitDepth(bufferPtr, outputBitDepth, channels, samplesPtr); } break; } case AudioBitDepth.Bits24: { // 24 bit -> target bit depth int[] samples = new int[channels]; for (int j = 0; j < channels; j++) { samples[j] = ToInt24(bufferPtr, j * 3); } fixed(int *samplesPtr = samples) { ChangeBitDepth24(bufferPtr, outputBitDepth, channels, samplesPtr); } break; } case AudioBitDepth.Bits32: { // 32 bit -> target bit depth int[] samples = new int[channels]; fixed(int *samplesPtr = samples) { MemoryOperations.Copy((byte *)samplesPtr, bufferPtr, channels * sizeof(int)); ChangeBitDepth(bufferPtr, outputBitDepth, channels, samplesPtr); } break; } } }