package miniaudio

import c "core:c/libc"

when ODIN_OS == .Windows {
	foreign import lib "lib/miniaudio.lib"
} else when ODIN_OS == .Linux {
	foreign import lib "lib/miniaudio.a"
} else {
	foreign import lib "system:miniaudio"
}

/*
Delay
*/
delay_config :: struct {
	channels:      u32,
	sampleRate:    u32,
	delayInFrames: u32,
	delayStart:    b32,    /* Set to true to delay the start of the output; false otherwise. */
	wet:           f32,    /* 0..1. Default = 1. */
	dry:           f32,    /* 0..1. Default = 1. */
	decay:         f32,    /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */
}

delay :: struct {
	config: delay_config,
	cursor: u32,               /* Feedback is written to this cursor. Always equal or in front of the read cursor. */
	bufferSizeInFrames: u32,   /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */
	pBuffer: [^]f32,
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	delay_config_init :: proc(channels, sampleRate, delayInFrames: u32, decay: f32) -> delay_config ---

	delay_init               :: proc(pConfig: ^delay_config, pAllocationCallbacks: ^allocation_callbacks, pDelay: ^delay) -> result ---
	delay_uninit             :: proc(pDelay: ^delay, pAllocationCallbacks: ^allocation_callbacks) ---
	delay_process_pcm_frames :: proc(pDelay: ^delay, pFramesOut, pFramesIn: rawptr, frameCount: u32) -> result ---
	delay_set_wet            :: proc(pDelay: ^delay, value: f32) ---
	delay_get_wet            :: proc(pDelay: ^delay) -> f32 ---
	delay_set_dry            :: proc(pDelay: ^delay, value: f32) ---
	delay_get_dry            :: proc(pDelay: ^delay) -> f32 ---
	delay_set_decay          :: proc(pDelay: ^delay, value: f32) ---
	delay_get_decay          :: proc(pDelay: ^delay) -> f32 ---
}


/* Gainer for smooth volume changes. */
gainer_config :: struct {
	channels: u32,
	smoothTimeInFrames: u32,
}

gainer :: struct {
	config:    gainer_config,
	t:         u32,
	pOldGains: [^]f32,
	pNewGains: [^]f32,

	/* Memory management. */
	_pHeap:    rawptr,
	_ownsHeap: b32,
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	gainer_config_init :: proc(channels, smoothTimeInFrames: u32) -> gainer_config ---

	gainer_get_heap_size      :: proc(pConfig: ^gainer_config, pHeapSizeInBytes: ^c.size_t) -> result ---
	gainer_init_preallocated  :: proc(pConfig: ^gainer_config, pHeap: rawptr, pGainer: ^gainer) -> result ---
	gainer_init               :: proc(pConfig: ^gainer_config, pAllocationCallbacks: ^allocation_callbacks, pGainer: ^gainer) -> result ---
	gainer_uninit             :: proc(pGainer: ^gainer, pAllocationCallbacks: ^allocation_callbacks) ---
	gainer_process_pcm_frames :: proc(pGainer: ^gainer, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result ---
	gainer_set_gain           :: proc(pGainer: ^gainer, newGain: f32) -> result ---
	gainer_set_gains          :: proc(pGainer: ^gainer, pNewGains: [^]f32) -> result ---
}


/* Stereo panner. */
pan_mode :: enum c.int {
	balance = 0,    /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */
	pan,            /* A true pan. The sound from one side will "move" to the other side and blend with it. */
}

panner_config :: struct {
	format:   format,
	channels: u32,
	mode:     pan_mode,
	pan:      f32,
}

panner :: struct {
	format:   format,
	channels: u32,
	mode:     pan_mode,
	pan:      f32,  /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	panner_config_init :: proc(format: format, channels: u32) -> panner_config ---

	panner_init               :: proc(pConfig: ^panner_config, pPanner: ^panner) -> result ---
	panner_process_pcm_frames :: proc(pPanner: ^panner, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result ---
	panner_set_mode           :: proc(pPanner: ^panner, mode: pan_mode) ---
	panner_get_mode           :: proc(pPanner: ^panner) -> pan_mode ---
	panner_set_pan            :: proc(pPanner: ^panner, pan: f32) ---
	panner_get_pan            :: proc(pPanner: ^panner) -> f32 ---
}


/* Fader. */
fader_config :: struct {
	format:     format,
	channels:   u32,
	sampleRate: u32,
}

fader :: struct {
	config:         fader_config,
	volumeBeg:      f32,    /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */
	volumeEnd:      f32,
	lengthInFrames: u64,    /* The total length of the fade. */
	cursorInFrames: u64,    /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	fader_config_init :: proc(format: format, channels, sampleRate: u32) -> fader_config ---

	fader_init               :: proc(pConfig: ^fader_config, pFader: ^fader) -> result ---
	fader_process_pcm_frames :: proc(pFader: ^fader, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result ---
	fader_get_data_format    :: proc(pFader: ^fader, pFormat: ^format, pChannels, pSampleRate: ^u32) ---
	fader_set_fade           :: proc(pFader: ^fader, volumeBeg, volumeEnd: f32, lengthInFrames: u64) ---
	fader_get_current_volume :: proc(pFader: ^fader) -> f32 ---
}


/* Spatializer. */
vec3f :: struct {
	x: f32,
	y: f32,
	z: f32,
}

attenuation_model :: enum c.int {
	none,          /* No distance attenuation and no spatialization. */
	inverse,       /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */
	linear,        /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */
	exponential,   /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */
}

positioning :: enum c.int {
	absolute,
	relative,
}

handedness :: enum c.int {
	right,
	left,
}

spatializer_listener_config :: struct {
	channelsOut:             u32,
	pChannelMapOut:          [^]channel,
	handedness:              handedness,   /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
	coneInnerAngleInRadians: f32,
	coneOuterAngleInRadians: f32,
	coneOuterGain:           f32,
	speedOfSound:            f32,
	worldUp:                 vec3f,
}

spatializer_listener :: struct {
		config:    spatializer_listener_config,
		position:  vec3f,  /* The absolute position of the listener. */
		direction: vec3f,  /* The direction the listener is facing. The world up vector is config.worldUp. */
		velocity:  vec3f,
		isEnabled: b32,

		/* Memory management. */
		_ownsHeap: b32,
		_pHeap:    rawptr,
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	spatializer_listener_config_init :: proc(channelsOut: u32) -> spatializer_listener_config ---

	spatializer_listener_get_heap_size      :: proc(pConfig: ^spatializer_listener_config, pHeapSizeInBytes: ^c.size_t) -> result ---
	spatializer_listener_init_preallocated  :: proc(pConfig: ^spatializer_listener_config, pHeap: rawptr, pListener: ^spatializer_listener) -> result ---
	spatializer_listener_init               :: proc(pConfig: ^spatializer_listener_config, pAllocationCallbacks: ^allocation_callbacks, pListener: ^spatializer_listener) -> result ---
	spatializer_listener_uninit             :: proc(pListener: ^spatializer_listener, pAllocationCallbacks: ^allocation_callbacks) ---
	spatializer_listener_get_channel_map    :: proc(pListener: ^spatializer_listener) -> ^channel ---
	spatializer_listener_set_cone           :: proc(pListener: ^spatializer_listener, innerAngleInRadians, outerAngleInRadians, outerGain: f32) ---
	spatializer_listener_get_cone           :: proc(pListener: ^spatializer_listener, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) ---
	spatializer_listener_set_position       :: proc(pListener: ^spatializer_listener, x, y, z: f32) ---
	spatializer_listener_get_position       :: proc(pListener: ^spatializer_listener) -> vec3f ---
	spatializer_listener_set_direction      :: proc(pListener: ^spatializer_listener, x, y, z: f32) ---
	spatializer_listener_get_direction      :: proc(pListener: ^spatializer_listener) -> vec3f ---
	spatializer_listener_set_velocity       :: proc(pListener: ^spatializer_listener, x, y, z: f32) ---
	spatializer_listener_get_velocity       :: proc(pListener: ^spatializer_listener) -> vec3f ---
	spatializer_listener_set_speed_of_sound :: proc(pListener: ^spatializer_listener, speedOfSound: f32) ---
	spatializer_listener_get_speed_of_sound :: proc(pListener: ^spatializer_listener) -> f32 ---
	spatializer_listener_set_world_up       :: proc(pListener: ^spatializer_listener, x, y, z: f32) ---
	spatializer_listener_get_world_up       :: proc(pListener: ^spatializer_listener) -> vec3f ---
	spatializer_listener_set_enabled        :: proc(pListener: ^spatializer_listener, isEnabled: b32) ---
	spatializer_listener_is_enabled         :: proc(pListener: ^spatializer_listener) -> b32 ---
}

spatializer_config :: struct {
	channelsIn:                   u32,
	channelsOut:                  u32,
	pChannelMapIn:                [^]channel,
	attenuationModel:             attenuation_model,
	positioning:                  positioning,
	handedness:                   handedness,    /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
	minGain:                      f32,
	maxGain:                      f32,
	minDistance:                  f32,
	maxDistance:                  f32,
	rolloff:                      f32,
	coneInnerAngleInRadians:      f32,
	coneOuterAngleInRadians:      f32,
	coneOuterGain:                f32,
	dopplerFactor:                f32,    /* Set to 0 to disable doppler effect. */
	directionalAttenuationFactor: f32,    /* Set to 0 to disable directional attenuation. */
	gainSmoothTimeInFrames:       u32,    /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */
}

spatializer :: struct {
		channelsIn:                   u32,
		channelsOut:                  u32,
		pChannelMapIn:                [^]channel,
		attenuationModel:             attenuation_model,
		positioning:                  positioning,
		handedness:                   handedness,    /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
		minGain:                      f32,
		maxGain:                      f32,
		minDistance:                  f32,
		maxDistance:                  f32,
		rolloff:                      f32,
		coneInnerAngleInRadians:      f32,
		coneOuterAngleInRadians:      f32,
		coneOuterGain:                f32,
		dopplerFactor:                f32,      /* Set to 0 to disable doppler effect. */
		directionalAttenuationFactor: f32,      /* Set to 0 to disable directional attenuation. */
		gainSmoothTimeInFrames:       u32,      /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */
		position:                     vec3f,
		direction:                    vec3f,
		velocity:                     vec3f,    /* For doppler effect. */
		dopplerPitch:                 f32,      /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */
		gainer:                       gainer,   /* For smooth gain transitions. */
		pNewChannelGainsOut:          [^]f32,     /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */

		/* Memory management. */
		_pHeap:    rawptr,
		_ownsHeap: b32,
}

@(default_calling_convention="c", link_prefix="ma_")
foreign lib {
	spatializer_config_init :: proc(channelsIn, channelsOut: u32) -> spatializer_config ---

	spatializer_get_heap_size                       :: proc(pConfig: ^spatializer_config, pHeapSizeInBytes: ^c.size_t) -> result ---
	spatializer_init_preallocated                   :: proc(pConfig: ^spatializer_config, pHeap: rawptr, pSpatializer: ^spatializer) -> result ---
	spatializer_init                                :: proc(pConfig: ^spatializer_config, pAllocationCallbacks: ^allocation_callbacks, pSpatializer: ^spatializer) -> result ---
	spatializer_uninit                              :: proc(pSpatializer: ^spatializer, pAllocationCallbacks: ^allocation_callbacks) ---
	spatializer_process_pcm_frames                  :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result ---
	spatializer_get_input_channels                  :: proc(pSpatializer: ^spatializer) -> u32 ---
	spatializer_get_output_channels                 :: proc(pSpatializer: ^spatializer) -> u32 ---
	spatializer_set_attenuation_model               :: proc(pSpatializer: ^spatializer, attenuationModel: attenuation_model) ---
	spatializer_get_attenuation_model               :: proc(pSpatializer: ^spatializer) -> attenuation_model ---
	spatializer_set_positioning                     :: proc(pSpatializer: ^spatializer, positioning: positioning) ---
	spatializer_get_positioning                     :: proc(pSpatializer: ^spatializer) -> positioning ---
	spatializer_set_rolloff                         :: proc(pSpatializer: ^spatializer, rolloff: f32) ---
	spatializer_get_rolloff                         :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_min_gain                        :: proc(pSpatializer: ^spatializer, minGain: f32) ---
	spatializer_get_min_gain                        :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_max_gain                        :: proc(pSpatializer: ^spatializer, maxGain: f32) ---
	spatializer_get_max_gain                        :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_min_distance                    :: proc(pSpatializer: ^spatializer, minDistance: f32) ---
	spatializer_get_min_distance                    :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_max_distance                    :: proc(pSpatializer: ^spatializer, maxDistance: f32) ---
	spatializer_get_max_distance                    :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_cone                            :: proc(pSpatializer: ^spatializer, innerAngleInRadians, outerAngleInRadians, outerGain: f32) ---
	spatializer_get_cone                            :: proc(pSpatializer: ^spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) ---
	spatializer_set_doppler_factor                  :: proc(pSpatializer: ^spatializer, dopplerFactor: f32) ---
	spatializer_get_doppler_factor                  :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_directional_attenuation_factor  :: proc(pSpatializer: ^spatializer, directionalAttenuationFactor: f32) ---
	spatializer_get_directional_attenuation_factor  :: proc(pSpatializer: ^spatializer) -> f32 ---
	spatializer_set_position                        :: proc(pSpatializer: ^spatializer, x, y, z: f32) ---
	spatializer_get_position                        :: proc(pSpatializer: ^spatializer) -> vec3f ---
	spatializer_set_direction                       :: proc(pSpatializer: ^spatializer, x, y, z: f32) ---
	spatializer_get_direction                       :: proc(pSpatializer: ^spatializer) -> vec3f ---
	spatializer_set_velocity                        :: proc(pSpatializer: ^spatializer, x, y, z: f32) ---
	spatializer_get_velocity                        :: proc(pSpatializer: ^spatializer) -> vec3f ---
	spatializer_get_relative_position_and_direction :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pRelativePos, pRelativeDir: ^vec3f) ---
}
