private static short AdpcmMsExpandNibble(ref adpcmMsChannel channel, byte nibble) { // Get a signed number out of the nibble. We need to retain the // original nibble value for when we access AdaptionTable[]. sbyte signedNibble = (sbyte)nibble; if ((signedNibble & 0x8) == 0x8) { signedNibble -= 0x10; } // Calculate new sample int predictor = (channel.Sample1 * channel.Coeff1 + channel.Sample2 * channel.Coeff2) / 256 + signedNibble * channel.Delta; // Clamp result to 16-bit, -32768 - 32767 predictor = Clamp(predictor, short.MinValue, short.MaxValue); // Shuffle samples, get new delta channel.Sample2 = channel.Sample1; channel.Sample1 = (short)predictor; channel.Delta = (short)((MSAdaptationTable[nibble] * channel.Delta) / 256); // Saturate the delta to a lower bound of 16 if (channel.Delta < 16) { channel.Delta = 16; } return((short)predictor); }
private static void DecodeAdpcmMs(Decoder decoder, BinaryReader reader, BinaryWriter writer) { // https://wiki.multimedia.cx/index.php/Microsoft_ADPCM // see also https://github.com/DeltaEngine/DeltaEngine/blob/master/Multimedia/OpenAL/Helpers/MsAdpcmConverter.cs DecoderState state = decoder.State; adpcmMsChannel[] channel = new adpcmMsChannel[2]; byte blockPredictor = 0; // determine total number of samples in this block // the initial 2 samples from the block preamble are sent directly to the output. // therefore, deduct 2 from the samples per block to calculate the remaining samples int totalSamples = (state.SamplesPerBlock - 2) * decoder.AudioFormat.Channels; if (totalSamples < 2) { return; } bool isStereo = decoder.AudioFormat.Channels == 2 ? true : false; // read predicates and deltas blockPredictor = reader.ReadByte(); blockPredictor = (byte)Clamp(blockPredictor, 0, 6); channel[0].Coeff1 = (short)MSAdaptationCoeff1[blockPredictor]; channel[0].Coeff2 = (short)MSAdaptationCoeff2[blockPredictor]; if (isStereo) { blockPredictor = reader.ReadByte(); blockPredictor = (byte)Clamp(blockPredictor, 0, 6); channel[1].Coeff1 = (short)MSAdaptationCoeff1[blockPredictor]; channel[1].Coeff2 = (short)MSAdaptationCoeff2[blockPredictor]; } channel[0].Delta = reader.ReadInt16(); if (isStereo) { channel[1].Delta = reader.ReadInt16(); } // read first samples and write them to result channel[0].Sample1 = reader.ReadInt16(); if (isStereo) { channel[1].Sample1 = reader.ReadInt16(); } channel[0].Sample2 = reader.ReadInt16(); if (isStereo) { channel[1].Sample2 = reader.ReadInt16(); } // output the samples if (isStereo) { writer.Write(channel[0].Sample2); writer.Write(channel[1].Sample2); writer.Write(channel[0].Sample1); writer.Write(channel[1].Sample1); } else { writer.Write(channel[0].Sample2); writer.Write(channel[0].Sample1); } // decode the rest of the samples for (int index = 0; index < totalSamples; index += 2) { try { byte nibble = reader.ReadByte(); writer.Write(AdpcmMsExpandNibble(ref channel[0], (byte)(nibble >> 4))); writer.Write(AdpcmMsExpandNibble(ref channel[isStereo ? 1 : 0], (byte)(nibble & 0x0f))); } catch (System.IO.EndOfStreamException) { Log.Verbose("DecodeAdpcmMs: Reached end of stream - returning."); break; } } }