// Package microphone implements a driver for a PDM microphone.
// For example, the Adafruit PDM MEMS breakout board (https://www.adafruit.com/product/3492)
//
// Datasheet: https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf
package microphone // import "tinygo.org/x/drivers/microphone"

import (
	"machine"
	"math"
)

const (
	defaultSampleRate        = 22000
	quantizeSteps            = 64
	msForSPLSample           = 50
	defaultSampleCountForSPL = (defaultSampleRate / 1000) * msForSPLSample
	defaultGain              = 9.0
	defaultRefLevel          = 0.00002
)

// Device wraps an I2S connection to a PDM microphone device.
type Device struct {
	bus machine.I2S

	// data buffer used for SPL sound pressure level samples
	data []int32

	// buf buffer used for sinc filter
	buf []uint32

	// SampleCountForSPL is number of samples aka size of data buffer to be used
	// for sound pressure level measurement.
	// Once Configure() is called, changing this value has no effect.
	SampleCountForSPL int

	// Gain setting used to calculate sound pressure level
	Gain float64

	// ReferenceLevel setting used to calculate sound pressure level.
	ReferenceLevel float64
}

// New creates a new microphone connection. The I2S bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2S) Device {
	return Device{
		bus:               bus,
		SampleCountForSPL: defaultSampleCountForSPL,
		Gain:              defaultGain,
		ReferenceLevel:    defaultRefLevel,
	}
}

// Configure the microphone.
func (d *Device) Configure() {
	d.data = make([]int32, d.SampleCountForSPL)
	d.buf = make([]uint32, (quantizeSteps / 16))
}

// Read the raw microphone data.
func (d *Device) Read(r []int32) (int, error) {
	count := len(r)

	// get the next group of samples
	machine.I2S0.ReadStereo(d.buf)

	if len(r) > len(d.buf) {
		count = len(d.buf)
	}
	for i := 0; i < count; i++ {
		r[i] = int32(d.buf[i])
	}

	return count, nil
}

// ReadWithFilter reads the microphone and filters the buffer using the sinc filter.
func (d *Device) ReadWithFilter(r []int32) (int, error) {
	// read/filter the samples
	var sum uint16
	for i := 0; i < len(r); i++ {

		// get the next group of samples
		machine.I2S0.ReadStereo(d.buf)

		// filter
		sum = applySincFilter(d.buf)

		// adjust to 10 bit value
		s := int32(sum >> 6)

		// make it close to 0-offset signed
		s -= 512

		r[i] = s
	}

	return len(r), nil
}

// GetSoundPressure returns the sound pressure in milli-decibels.
func (d *Device) GetSoundPressure() (int32, int32) {
	// read/filter the samples
	d.ReadWithFilter(d.data)

	// remove offset
	var avg int32
	for i := 0; i < len(d.data); i++ {
		avg += d.data[i]
	}
	avg /= int32(len(d.data))

	for i := 0; i < len(d.data); i++ {
		d.data[i] -= avg
	}

	// get max value
	var maxval int32
	for i := 0; i < len(d.data); i++ {
		v := d.data[i]
		if v < 0 {
			v = -v
		}
		if maxval < v {
			maxval = v
		}
	}

	// calculate SPL
	spl := float64(maxval) / 1023.0 * d.Gain
	spl = 20 * math.Log10(spl/d.ReferenceLevel)

	return int32(spl * 1000), maxval
}

// sinc filter for 44 khz with 64 samples
// each value matches the corresponding bit in the 8-bit value
// for that sample.
//
// For more information: https://en.wikipedia.org/wiki/Sinc_filter
var sincfilter = [quantizeSteps]uint16{
	0, 2, 9, 21, 39, 63, 94, 132,
	179, 236, 302, 379, 467, 565, 674, 792,
	920, 1055, 1196, 1341, 1487, 1633, 1776, 1913,
	2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516,
	2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913,
	1776, 1633, 1487, 1341, 1196, 1055, 920, 792,
	674, 565, 467, 379, 302, 236, 179, 132,
	94, 63, 39, 21, 9, 2, 0, 0,
}

// applySincFilter uses the sinc filter to process a single set of sample values.
func applySincFilter(samples []uint32) (result uint16) {
	var sample uint16
	pos := 0
	for j := 0; j < len(samples); j++ {
		// takes only the low order 16-bits
		sample = uint16(samples[j] & 0xffff)
		for i := 0; i < 16; i++ {
			if (sample & 0x1) > 0 {
				result += sincfilter[pos]
				pos++
			}
			sample >>= 1
		}
	}

	return
}
