public function getSampleValue($blockNum, $channelNum)
    {
        // check preconditions
        if ($channelNum < 1 || $channelNum > $this->_numChannels) {
            throw new WavFileException('Channel number is out of range.');
        }

        if (!$this->_dataSize_valid) {
            $this->setDataSize();  // implicit setSize(), setActualSize(), setNumBlocks()
        }

        $sampleBytes = $this->_bitsPerSample / 8;
        $offset = $blockNum * $this->_blockAlign + ($channelNum - 1) * $sampleBytes;
        if ($offset + $sampleBytes > $this->_dataSize || $offset < 0) {
            return null;
        }

        // read binary value
        $sampleBinary = substr($this->_samples, $offset, $sampleBytes);

        // convert binary to value
        switch ($this->_bitsPerSample) {
            case 8:
                // unsigned char
                return (float)((ord($sampleBinary) - 0x80) / 0x80);

            case 16:
                // signed short, little endian
                $data = unpack('v', $sampleBinary);
                $sample = $data[1];
                if ($sample >= 0x8000) {
                    $sample -= 0x10000;
                }
                return (float)($sample / 0x8000);

            case 24:
                // 3 byte packed signed integer, little endian
                $data = unpack('C3', $sampleBinary);
                $sample = $data[1] | ($data[2] << 8) | ($data[3] << 16);
                if ($sample >= 0x800000) {
                    $sample -= 0x1000000;
                }
                return (float)($sample / 0x800000);

            case 32:
                // 32-bit float
                $data = unpack('f', $sampleBinary);
                return (float)$data[1];

            default:
                return null;
        }
    }