Exemple #1
0
        /// <summary>
        /// Exports data to a byte array in RIFF WAVE (.wav) format.
        /// Output data will use WAVE_FORMAT_PCM and have "fmt " and "data" chunks, along with a "smpl" chunk if it is a looping track.
        /// Note that even files with more than 2 channels will use WAVE_FORMAT_PCM as the format, even though doing this is invalid according to the spec.
        /// </summary>
        /// <param name="lwav"></param>
        /// <returns></returns>
        public static unsafe byte[] Export(this PCM16Audio lwav)
        {
            int length = 12 + 8 + sizeof(fmt) + 8 + (lwav.Samples.Length * 2);

            if (lwav.Looping)
            {
                length += 8 + sizeof(smpl) + sizeof(smpl_loop);
            }
            byte[] data = new byte[length];
            fixed(byte *start = data)
            {
                byte *ptr = start;

                *(int *)ptr = tag("RIFF");
                ptr        += 4;
                *(int *)ptr = length - 8;
                ptr        += 4;
                *(int *)ptr = tag("WAVE");
                ptr        += 4;

                *(int *)ptr = tag("fmt ");
                ptr        += 4;
                *(int *)ptr = sizeof(fmt);
                ptr        += 4;

                fmt *fmt = (fmt *)ptr;

                fmt->format        = 1;
                fmt->channels      = lwav.Channels;
                fmt->sampleRate    = lwav.SampleRate;
                fmt->byteRate      = lwav.SampleRate * lwav.Channels * 2;
                fmt->blockAlign    = (short)(lwav.Channels * 2);
                fmt->bitsPerSample = 16;
                ptr += sizeof(fmt);

                *(int *)ptr = tag("data");
                ptr        += 4;
                *(int *)ptr = lwav.Samples.Length * 2;
                ptr        += 4;

                Marshal.Copy(lwav.Samples, 0, (IntPtr)ptr, lwav.Samples.Length);
                ptr += lwav.Samples.Length * 2;

                if (lwav.Looping)
                {
                    *(int *)ptr = tag("smpl");
                    ptr        += 4;
                    *(int *)ptr = sizeof(smpl) + sizeof(smpl_loop);
                    ptr        += 4;

                    smpl *smpl = (smpl *)ptr;
                    smpl->sampleLoopCount = 1;
                    ptr += sizeof(smpl);

                    smpl_loop *loop = (smpl_loop *)ptr;
                    loop->loopID    = 0;
                    loop->type      = 0;
                    loop->start     = lwav.LoopStart;
                    loop->end       = lwav.LoopEnd;
                    loop->fraction  = 0;
                    loop->playCount = 0;
                    ptr            += sizeof(smpl_loop);
                }
                return(data);
            }
        }
Exemple #2
0
        /// <summary>
        /// Reads RIFF WAVE data from a stream.
        /// If the size of the "data" chunk is incorrect or negative, but the "data" chunk is known to be the last chunk in the file, set the assumeDataIsLastChunk parameter to true.
        /// </summary>
        /// <param name="stream">Stream to read from (no data will be written to the stream)</param>
        /// <returns></returns>
        public unsafe static PCM16Audio FromStream(Stream stream)
        {
            byte[] buffer = new byte[12];
            int    r      = stream.Read(buffer, 0, 12);

            if (r == 0)
            {
                throw new PCM16FactoryException("No data in stream");
            }
            else if (r < 12)
            {
                throw new PCM16FactoryException("Unexpected end of stream in first 12 bytes");
            }

            fixed(byte *bptr = buffer)
            {
                if (*(int *)bptr != tag("RIFF"))
                {
                    throw new PCM16FactoryException("RIFF header not found");
                }
                if (*(int *)(bptr + 8) != tag("WAVE"))
                {
                    throw new PCM16FactoryException("WAVE header not found");
                }
            }

            int channels   = 0;
            int sampleRate = 0;

            short[] sample_data        = null;
            bool    convert_from_8_bit = false;

            int?loopStart = null;
            int?loopEnd   = null;

            // Keep reading chunk headers into a buffer of 8 bytes
            while ((r = stream.Read(buffer, 0, 8)) > 0)
            {
                if (r < 8)
                {
                    throw new PCM16FactoryException("Unexpected end of stream in chunk header");
                }
                else
                {
                    fixed(byte *ptr1 = buffer)
                    {
                        // Four ASCII characters
                        string id = Marshal.PtrToStringAnsi((IntPtr)ptr1, 4);

                        int chunklength = *(int *)(ptr1 + 4);

                        byte[] buffer2;
                        if (id == "data" && chunklength == -1)
                        {
                            // Special handling for streaming output of madplay.exe.
                            // If we were using temporary files, this would not be needed, but I like a good programming challenge.
                            using (MemoryStream ms = new MemoryStream()) {
                                byte[] databuffer = new byte[1024 * 1024];
                                while ((r = stream.Read(databuffer, 0, databuffer.Length)) > 0)
                                {
                                    ms.Write(databuffer, 0, r);
                                }

                                buffer2 = ms.ToArray();
                            }
                        }
                        else
                        {
                            // Look at the length of the chunk and read that many bytes into a byte array.
                            buffer2 = new byte[chunklength];
                            int total = 0;
                            while (total < buffer2.Length)
                            {
                                total += (r = stream.Read(buffer2, total, buffer2.Length - total));
                                if (r == 0)
                                {
                                    throw new PCM16FactoryException("Unexpected end of data in \"" + Marshal.PtrToStringAnsi((IntPtr)ptr1, 4) + "\" chunk: expected " + buffer2.Length + " bytes, got " + total + " bytes");
                                }
                            }
                        }

                        fixed(byte *ptr2 = buffer2)
                        {
                            if (id == "fmt ")
                            {
                                // Format chunk
                                fmt *fmt = (fmt *)ptr2;
                                if (fmt->format != 1)
                                {
                                    if (fmt->format == 65534)
                                    {
                                        // WAVEFORMATEXTENSIBLE
                                        fmt_extensible *ext = (fmt_extensible *)fmt;
                                        if (ext->subFormat == new Guid("00000001-0000-0010-8000-00aa00389b71"))
                                        {
                                            // KSDATAFORMAT_SUBTYPE_PCM
                                        }
                                        else
                                        {
                                            throw new PCM16FactoryException("Only uncompressed PCM suppported - found WAVEFORMATEXTENSIBLE with subformat " + ext->subFormat);
                                        }
                                    }
                                    else
                                    {
                                        throw new PCM16FactoryException("Only uncompressed PCM suppported - found format " + fmt->format);
                                    }
                                }
                                else if (fmt->bitsPerSample != 16)
                                {
                                    if (fmt->bitsPerSample == 8)
                                    {
                                        convert_from_8_bit = true;
                                    }
                                    else
                                    {
                                        throw new PCM16FactoryException("Only 16-bit wave files supported");
                                    }
                                }

                                channels   = fmt->channels;
                                sampleRate = fmt->sampleRate;
                            }
                            else if (id == "data")
                            {
                                // Data chunk - contains samples
                                sample_data = new short[buffer2.Length / 2];
                                Marshal.Copy((IntPtr)ptr2, sample_data, 0, sample_data.Length);
                            }
                            else if (id == "smpl")
                            {
                                // sampler chunk
                                smpl *smpl = (smpl *)ptr2;
                                if (smpl->sampleLoopCount > 1)
                                {
                                    throw new PCM16FactoryException("Cannot read looping .wav file with more than one loop");
                                }
                                else if (smpl->sampleLoopCount == 1)
                                {
                                    // There is one loop - we only care about start and end points
                                    smpl_loop *loop = (smpl_loop *)(smpl + 1);
                                    if (loop->type != 0)
                                    {
                                        throw new PCM16FactoryException("Cannot read looping .wav file with loop of type " + loop->type);
                                    }
                                    loopStart = loop->start;
                                    loopEnd   = loop->end;
                                }
                            }
                            else
                            {
                                Console.Error.WriteLine("Ignoring unknown chunk " + id);
                            }
                        }
                    }
                }
            }

            if (sampleRate == 0)
            {
                throw new PCM16FactoryException("Format chunk not found");
            }
            if (sample_data == null)
            {
                throw new PCM16FactoryException("Data chunk not found");
            }

            if (convert_from_8_bit)
            {
                short[] new_sample_data = new short[sample_data.Length * 2];
                fixed(short *short_ptr = sample_data)
                {
                    byte *ptr = (byte *)short_ptr;

                    for (int i = 0; i < new_sample_data.Length; i++)
                    {
                        new_sample_data[i] = (short)((ptr[i] - 0x80) << 8);
                    }
                }

                sample_data = new_sample_data;
            }

            PCM16Audio wav = new PCM16Audio(channels, sampleRate, sample_data, loopStart, loopEnd);

            return(wav);
        }