<commit_before>package scWriter

import (
	"fmt"
	"log"
	"sync"
	"time"

	ringBuffer "github.com/dh1tw/golang-ring"
	"github.com/dh1tw/gosamplerate"
	"github.com/dh1tw/remoteAudio/audio"
	pa "github.com/gordonklaus/portaudio"
)

// ScWriter implements the audio.Sink interface and is used to write (play)
// audio on a local audio output device (e.g. speakers).
type ScWriter struct {
	sync.RWMutex
	options    Options
	deviceInfo *pa.DeviceInfo
	stream     *pa.Stream
	ring       ringBuffer.Ring
	stash      []float32
	volume     float32
	src        src
}

// src contains a samplerate converter and its needed variables
type src struct {
	gosamplerate.Src
	samplerate float64
	ratio      float64
}

// NewScWriter returns a new soundcard writer for a specific audio output
// device. This is typically a speaker or a pair of headphones.
func NewScWriter(opts ...Option) (*ScWriter, error) {

	if err := pa.Initialize(); err != nil {
		return nil, err
	}

	info, err := pa.DefaultOutputDevice()
	if err != nil {
		return nil, err
	}

	w := &ScWriter{
		options: Options{
			DeviceName:      "default",
			Channels:        2,
			Samplerate:      48000,
			FramesPerBuffer: 480,
			RingBufferSize:  10,
			Latency:         time.Millisecond * 10,
		},
		deviceInfo: info,
		ring:       ringBuffer.Ring{},
		volume:     0.7,
	}

	for _, option := range opts {
		option(&w.options)
	}

	// setup a samplerate converter
	srConv, err := gosamplerate.New(gosamplerate.SRC_SINC_FASTEST, w.options.Channels, 65536)
	if err != nil {
		return nil, fmt.Errorf("player: %v", err)
	}

	w.src = src{
		Src:        srConv,
		samplerate: w.options.Samplerate,
		ratio:      1,
	}

	// select Playback Audio Device
	if w.options.DeviceName != "default" {
		device, err := getPaDevice(w.options.DeviceName)
		if err != nil {
			return nil, err
		}
		w.deviceInfo = device
	}

	// setup Audio Stream
	streamDeviceParam := pa.StreamDeviceParameters{
		Device:   w.deviceInfo,
		Channels: w.options.Channels,
		Latency:  w.options.Latency,
	}

	streamParm := pa.StreamParameters{
		FramesPerBuffer: w.options.FramesPerBuffer,
		Output:          streamDeviceParam,
		SampleRate:      w.options.Samplerate,
	}

	// setup ring buffer
	w.ring.SetCapacity(w.options.RingBufferSize)

	stream, err := pa.OpenStream(streamParm, w.playCb)
	if err != nil {
		return nil,
			fmt.Errorf("unable to open playback audio stream on device %s: %s",
				w.options.DeviceName, err)
	}

	w.stream = stream

	return w, nil
}

// portaudio callback which will be called continously when the stream is
// started; this function should be short and never block
func (p *ScWriter) playCb(in []float32,
	iTime pa.StreamCallbackTimeInfo,
	iFlags pa.StreamCallbackFlags) {
	switch iFlags {
	case pa.OutputUnderflow:
		log.Println("Output Underflow")
		return // move on!
	case pa.OutputOverflow:
		log.Println("Output Overflow")
		return // move on!
	}

	//pull data from Ringbuffer
	p.Lock()
	data := p.ring.Dequeue()
	p.Unlock()

	if data == nil {
		// fill with silence
		// fmt.Println("silence")
		for i := 0; i < len(in); i++ {
			in[i] = 0
		}
		return
	}

	audioData := data.([]float32)

	// should never happen
	if len(audioData) != len(in) {
		log.Printf("unable to play audio frame; expected frame size %d, but got %d",<commit_msg>filling buffers to 50% when ringbuffer becomes empty to reduce dropouts<commit_after>package scWriter

import (
	"fmt"
	"log"
	"sync"
	"time"

	ringBuffer "github.com/dh1tw/golang-ring"
	"github.com/dh1tw/gosamplerate"
	"github.com/dh1tw/remoteAudio/audio"
	pa "github.com/gordonklaus/portaudio"
)

// ScWriter implements the audio.Sink interface and is used to write (play)
// audio on a local audio output device (e.g. speakers).
type ScWriter struct {
	sync.RWMutex
	options    Options
	deviceInfo *pa.DeviceInfo
	stream     *pa.Stream
	ring       ringBuffer.Ring
	stash      []float32
	volume     float32
	src        src
	fill       bool
}

// src contains a samplerate converter and its needed variables
type src struct {
	gosamplerate.Src
	samplerate float64
	ratio      float64
}

// NewScWriter returns a new soundcard writer for a specific audio output
// device. This is typically a speaker or a pair of headphones.
func NewScWriter(opts ...Option) (*ScWriter, error) {

	if err := pa.Initialize(); err != nil {
		return nil, err
	}

	info, err := pa.DefaultOutputDevice()
	if err != nil {
		return nil, err
	}

	w := &ScWriter{
		options: Options{
			DeviceName:      "default",
			Channels:        2,
			Samplerate:      48000,
			FramesPerBuffer: 480,
			RingBufferSize:  10,
			Latency:         time.Millisecond * 10,
		},
		deviceInfo: info,
		ring:       ringBuffer.Ring{},
		volume:     0.7,
	}

	for _, option := range opts {
		option(&w.options)
	}

	// setup a samplerate converter
	srConv, err := gosamplerate.New(gosamplerate.SRC_SINC_FASTEST, w.options.Channels, 65536)
	if err != nil {
		return nil, fmt.Errorf("player: %v", err)
	}

	w.src = src{
		Src:        srConv,
		samplerate: w.options.Samplerate,
		ratio:      1,
	}

	// select Playback Audio Device
	if w.options.DeviceName != "default" {
		device, err := getPaDevice(w.options.DeviceName)
		if err != nil {
			return nil, err
		}
		w.deviceInfo = device
	}

	// setup Audio Stream
	streamDeviceParam := pa.StreamDeviceParameters{
		Device:   w.deviceInfo,
		Channels: w.options.Channels,
		Latency:  w.options.Latency,
	}

	streamParm := pa.StreamParameters{
		FramesPerBuffer: w.options.FramesPerBuffer,
		Output:          streamDeviceParam,
		SampleRate:      w.options.Samplerate,
	}

	// setup ring buffer
	w.ring.SetCapacity(w.options.RingBufferSize)

	stream, err := pa.OpenStream(streamParm, w.playCb)
	if err != nil {
		return nil,
			fmt.Errorf("unable to open playback audio stream on device %s: %s",
				w.options.DeviceName, err)
	}

	w.stream = stream

	return w, nil
}

// portaudio callback which will be called continously when the stream is
// started; this function should be short and never block
func (p *ScWriter) playCb(in []float32,
	iTime pa.StreamCallbackTimeInfo,
	iFlags pa.StreamCallbackFlags) {
	switch iFlags {
	case pa.OutputUnderflow:
		log.Println("Output Underflow")
		return // move on!
	case pa.OutputOverflow:
		log.Println("Output Overflow")
		return // move on!
	}

	//pull data from Ringbuffer
	p.Lock()
	data := p.ring.Dequeue()
	bufCapacity := p.ring.Capacity()
	bufLength := p.ring.Length()
	p.Unlock()

	// log.Printf("Buf: %d / %d\n", bufLength, bufCapacity)

	if data == nil && bufLength == 0 {
		if !p.fill {
			// log.Println("start filling buffer")
		}
		p.fill = true
	}

	if bufLength >= bufCapacity/2 && p.fill {
		p.fill = false
		// log.Println("stop filling buffer")
	}

	if data == nil || p.fill {
		// fill with silence
		// fmt.Println("silence")
		for i := 0; i < len(in); i++ {
			in[i] = 0
		}
		return
	}

	audioData := data.([]float32)

	// should never happen
	if len(audioData) != len(in) {
		log.Printf("unable to play audio frame; expected frame size %d, but got %d",
{'fraction_non_alphanumeric': 0.1183685111237878, 'fraction_numerical': 0.011409013120365089, 'mean_word_length': 3.1083772700644405, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5493798', 'n_tokens_mistral': 2644, 'n_tokens_neox': 2410, 'n_words': 684}