/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2020  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

//#include <zglib.h>

#include "header.h"

#include "alsa.h"
#include "dsp.h" 
#include "fifo.h"
#include "language2.h"
#include "menu.h"

#ifdef HAVE_ALSA


static void device_info(GString *gs, int card, int pcm_device, snd_pcm_stream_t dir){
    int err;
    char pcm_name[256], mixer_name[256];
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_t *pcm;
    snd_mixer_t *mixer_handle;
    snd_mixer_elem_t *elem;
    snd_ctl_t *ctl_handle;
    snd_ctl_card_info_t *hw_info;
    int sw;
    const char *mixer_chip;

    sprintf(pcm_name, "hw:%d,%d", card, pcm_device);

    err=snd_pcm_open(&pcm, pcm_name, dir, 0);
    if (err<0) return;

    snd_pcm_hw_params_alloca(&hwparams);
    snd_ctl_card_info_alloca(&hw_info);

    err=snd_pcm_hw_params_any(pcm, hwparams);
    if (err<0) goto x;

/*    int rate_num,rate_den;
    err=snd_pcm_hw_params_get_rate_numden(hwparams, &rate_num, &rate_den);
    if (err<0) goto x;
    printf("        rate_num=%d rate_den=%d\n", rate_num, rate_den);
  */

    unsigned int channels_min = -1, channels = -1, channels_max = -1;
    err=snd_pcm_hw_params_get_channels_min(hwparams, &channels_min);
    //if (err<0) goto x1;
    err=snd_pcm_hw_params_get_channels(hwparams, &channels);
   // if (err<0) goto x1;
    err=snd_pcm_hw_params_get_channels_max(hwparams, &channels_max);
   // if (err<0) goto x1;
    g_string_append_printf(gs, "        channels=%d .. %d .. %d\n", channels_min, channels, channels_max);
//x1:;
    
    unsigned int rate_min, rate, rate_max;
    int dir_min, dir_, dir_max;
    err=snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, &dir_min);
    if (err<0) goto x2;
    err=snd_pcm_hw_params_get_rate(hwparams, &rate, &dir_);
    if (err<0) goto x2;
    err=snd_pcm_hw_params_get_rate_max(hwparams, &rate_max, &dir_max);
    if (err<0) goto x2;
    g_string_append_printf(gs, "        rate=%d .. %d .. %d\n", rate_min, rate, rate_max);
x2:

    if (dir == SND_PCM_STREAM_CAPTURE){
        sprintf(mixer_name, "hw:%d", card);

        snd_ctl_open(&ctl_handle, mixer_name, 0);
        if (err<0) goto x3c;
        err = snd_ctl_card_info(ctl_handle, hw_info);
        if (err<0){
            snd_ctl_close(ctl_handle);
            goto x3c;
        }
        mixer_chip=snd_ctl_card_info_get_mixername(hw_info);
        g_string_append_printf(gs, "        mixer chip=%s\n", mixer_chip);
        snd_ctl_close(ctl_handle);

x3c:        
        err=snd_mixer_open(&mixer_handle, 0);
        if (err<0) goto x3;
        err=snd_mixer_attach(mixer_handle, mixer_name);
        if (err<0) goto x3;
        err=snd_mixer_selem_register(mixer_handle, NULL, NULL);
        if (err<0) goto x3;
        err=snd_mixer_load(mixer_handle);
        if (err<0) goto x3;

        g_string_append_printf(gs, "        elements=");
        for (elem=snd_mixer_first_elem(mixer_handle);
             elem;
             elem=snd_mixer_elem_next(elem)){

            if (!snd_mixer_selem_has_capture_switch(elem)) continue;
                  
            err=snd_mixer_selem_get_capture_switch(elem, 0, &sw);
            if (err<0) goto x3;
            
            if (sw)
                g_string_append_printf(gs, "[%s] ", snd_mixer_selem_get_name(elem));
            else
                g_string_append_printf(gs, "'%s' ", snd_mixer_selem_get_name(elem));
        }
        g_string_append_printf(gs, "\n");

x3:
        snd_mixer_close(mixer_handle);
        
    }
    
x:
    snd_pcm_close(pcm);
    
}


static void card_info(GString *gs, int card){
    int pcm_device;
    char *name;
    snd_ctl_t *ctl;
    char dev[256];
    snd_pcm_info_t *pcm_info;

    g_string_append_printf(gs, "card hw:%d ",card);

    if (snd_card_get_name(card, &name)==0){
        g_string_append_printf(gs, "%s", name);
    }
    g_string_append_printf(gs, "\n");                

    sprintf(dev,"hw:%d", card);
    snd_ctl_open(&ctl, dev, 0);
    
    snd_pcm_info_alloca(&pcm_info);    

    pcm_device=-1;
    while(1){
        if (snd_ctl_pcm_next_device(ctl, &pcm_device)) pcm_device=-1;
        if (pcm_device<0) break;

        snd_pcm_info_set_device(pcm_info, pcm_device);
        snd_pcm_info_set_subdevice(pcm_info, 0);
        snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_PLAYBACK);
        
        if (snd_ctl_pcm_info(ctl, pcm_info)<0) continue;
        
        g_string_append_printf(gs, "    playback hw:%d,%d %s\n", card, pcm_device, snd_pcm_info_get_name(pcm_info)); 
        device_info(gs, card, pcm_device, SND_PCM_STREAM_PLAYBACK);
    }
    
    pcm_device=-1;
    while(1){
        if (snd_ctl_pcm_next_device(ctl, &pcm_device)) pcm_device=-1;
        if (pcm_device<0) break;

        snd_pcm_info_set_device(pcm_info, pcm_device);
        snd_pcm_info_set_subdevice(pcm_info, 0);
        snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_CAPTURE);
        
        if (snd_ctl_pcm_info(ctl, pcm_info)<0) continue;
        
        g_string_append_printf(gs, "    capture  hw:%d,%d %s\n", card, pcm_device, snd_pcm_info_get_name(pcm_info)); 
        device_info(gs, card, pcm_device, SND_PCM_STREAM_CAPTURE);
    }
}

void alsa_info(GString *gs){
    int card;
    
    g_string_append_printf(gs, "\n  alsa_info:\n");
    card=-1;
    snd_card_next(&card);
    while(card>-1){
        card_info(gs, card);
        snd_card_next(&card);
    }
    
    g_string_append_printf(gs, "\n");
}



int alsa_open(struct dsp *dsp, int rec){
    int err, ratedir;
    unsigned int rateval;
    snd_pcm_hw_params_t *hwparams;
    snd_pcm_sw_params_t *swparams;
    /*snd_pcm_uframes_t frames;*/
    unsigned buffer_time, period_time;
    int dir;
    unsigned int periods;
    snd_pcm_uframes_t buffer_size, period_size;


    if (dsp->pcm_opened) alsa_close(dsp);

    zg_free0(dsp->name);
    if (rec){
        dsp->name = g_strdup(dsp->pcm_rec);
    }else{
        dsp->name = g_strdup(dsp->pcm_play);
    }
    
    //dbg("alsa_open('%s', %s)\n", dsp->name, rec?"capture":"play");

    err=snd_pcm_open(&dsp->pcm, dsp->name, rec?SND_PCM_STREAM_CAPTURE:SND_PCM_STREAM_PLAYBACK, 0);
    if (err<0){
        log_addf(VTEXT(T_CANT_OPEN_ALSA_PCM_S_S), dsp->name, snd_strerror(-err));
        goto err;
    }
    //dbg("alsa opened (%s)\n", dsp->name);
    dsp->pcm_opened=1;
    
/*    err=snd_pcm_nonblock(dsp->pcm, 1);
    if (err<0){
        log_addf("alsa_open: snd_pcm_nonblock failed: %s", snd_strerror(-err));
        goto err;
    }  */
    
    snd_pcm_hw_params_alloca(&hwparams);
    snd_pcm_sw_params_alloca(&swparams);

    
    /* HW parameters */
    err=snd_pcm_hw_params_any(dsp->pcm, hwparams);
    if (err<0){
        log_addf("alsa_open: snd_pcm_hw_params_any failed: %s", snd_strerror(-err));
        goto err;
    }  
    /* read/write format */
    err=snd_pcm_hw_params_set_access(dsp->pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err<0){
        log_addf("alsa_open: snd_pcm_hw_params_set_access failed: %s", snd_strerror(-err));
        goto err;
    }  
    
    /* sample format */
    err=snd_pcm_hw_params_set_format(dsp->pcm, hwparams, dsp->pcm_format);
    if (err<0){
        char *c = "unknown";
        switch(dsp->pcm_format){
            case SND_PCM_FORMAT_MU_LAW: c = "MU_LAW"; break;
            case SND_PCM_FORMAT_A_LAW: c = "A_LAW"; break;
            case SND_PCM_FORMAT_IMA_ADPCM: c = "IMA_ADPCM"; break;
            case SND_PCM_FORMAT_S16_LE: c = "S16_LE"; break;
            default: c = "unknown"; break;
        }
        log_addf("alsa_open: snd_pcm_hw_params_set_format 0x%x (%s) failed: %s", dsp->pcm_format, c, snd_strerror(-err));
        goto err;
    }  
    
    /* channels */
    err=snd_pcm_hw_params_set_channels(dsp->pcm, hwparams, dsp->channels);
    if (err<0){
        log_addf("alsa_open: snd_pcm_hw_params_set_channels(%d) failed: %s", dsp->channels, snd_strerror(-err));
        goto err;
    }  
    
    /* sample rate */
    rateval=dsp->speed;
    ratedir=0;
    err=snd_pcm_hw_params_set_rate_near(dsp->pcm, hwparams, &rateval, &ratedir);
    if (err<0){
        log_addf("alsa_open: snd_pcm_hw_params_set_rate_near(%d) failed: %s", rateval, snd_strerror(-err));
        goto err;
    }  

    dir = 1; /* not sure if dir is input parameter. */
    //buffer_time=500000;
    period_time=100000;
    if (dsp->period_time>0) period_time=dsp->period_time*1000;

//	dbg("snd_pcm_hw_params_set_buffer_time_near(buffer_time=%d, dir=%d)\n", buffer_time, dir);

	/* set the period time */
//	dbg("snd_pcm_hw_params_set_period_time_near(period_time=%d, dir=%d\n", period_time, dir);
	err = snd_pcm_hw_params_set_period_time_near(dsp->pcm, hwparams, &period_time, &dir);
	if (err < 0) {
		log_addf("Unable to set period time %i: %s\n", period_time, snd_strerror(err));
		goto err;
	}
	err = snd_pcm_hw_params_get_period_time(hwparams, &period_time, &dir);
	if (err < 0) {
		log_addf("Unable to get period time: %s\n", snd_strerror(err));
		goto err;
	}

	dir = 0; // must be 0 otherwise here is not single value for buffer time on some cards
    periods = 2;
	err = snd_pcm_hw_params_set_periods(dsp->pcm, hwparams, periods, dir);
	if (err < 0) {
        /* set the buffer time */
        dir = 1;
        buffer_time = period_time * 2;
	    err = snd_pcm_hw_params_set_buffer_time_near(dsp->pcm, hwparams, &buffer_time, &dir);
        if (err < 0){
		    //log_addf("Unable to set buffer time %i: %s\n", buffer_time, snd_strerror(err));
		    //log_addf("Unable to set buffer period count %i and buffer time %d: %s\n", 2, snd_strerror(err), buffer_time);
		    goto err;
        }else{
            //log_addf("snd_pcm_hw_params_set_buffer_time_near(%d)", buffer_time);
        }
	}else{
        //log_addf("snd_pcm_hw_params_set_periods(%d) OK", periods);
    }
    dir = 0;
	err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, &dir);
	if (err < 0) {
		log_addf("Unable to get buffer time: %s\n", snd_strerror(err));

/*	    err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time, &dir);
        if (err == 0) log_addf("buffer_time_min=%d", buffer_time);
	    err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, &dir);
        if (err == 0) log_addf("buffer_time_max=%d", buffer_time);

	    err = snd_pcm_hw_params_get_period_time_min(hwparams, &period_time, &dir);
        if (err == 0) log_addf("period_time_min=%d", period_time);
	    err = snd_pcm_hw_params_get_period_time_max(hwparams, &period_time, &dir);
        if (err == 0) log_addf("period_time_max=%d", period_time);

	    err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size);
        if (err == 0) log_addf("buffer_size_min=%d", buffer_size);
	    err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size);
        if (err == 0) log_addf("buffer_size_max=%d", buffer_size);

	    err = snd_pcm_hw_params_get_period_size_min(hwparams, &period_size, &dir);
        if (err == 0) log_addf("period_size_min=%d", period_size);
	    err = snd_pcm_hw_params_get_period_size_max(hwparams, &period_size, &dir);
        if (err == 0) log_addf("period_size_max=%d", period_size);*/

		goto err;
	}
	err = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
	if (err < 0) {
		log_addf("Unable to get buffer size: %s\n", snd_strerror(err));
		goto err;
	}

    dir = 0;
	err = snd_pcm_hw_params_get_period_size(hwparams, &period_size, &dir);
	if (err < 0) {
		log_addf("Unable to get period size: %s\n", snd_strerror(err));
		goto err;
	}
	dbg("snd_pcm_hw_params_get_period_size() period_size=%d dir=%d\n", period_size, dir);
 
    /* write hwparams */
    err=snd_pcm_hw_params(dsp->pcm, hwparams);
    if (err<0){
        log_addf("alsa_open: snd_pcm_hw_params failed: %s", snd_strerror(-err));
        goto err;
    }  

    dsp->frames = period_size;
	dsp->samples = dsp->frames * dsp->channels;
	dsp->bytes = dsp->samples * sizeof(short);

    //log_addf("Alsa %s: period=%d (%1.1fms), buffer=%d (%1.1fms), bufsize=%d", rec ? "capt" : "play", period_size, period_time / 1000.0, buffer_size, buffer_time / 1000.0, dsp->bufsize);
    dbg("Alsa %s: period=%d (%1.1fms), buffer=%d (%1.1fms), bufsize=%df %ds %db\n", rec ? "capt" : "play", period_size, period_time / 1000.0, buffer_size, buffer_time / 1000.0, dsp->frames, dsp->samples, dsp->bytes);

    if (!rec){
        dsp_write_empty(dsp);
    }

/*
      // get the current swparams
	err = snd_pcm_sw_params_current(dsp->pcm, swparams);
	if (err < 0) {
		printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
		goto err;
	}
	// start the transfer when the buffer is almost full:
	// (buffer_size / avail_min) * avail_min
	err = snd_pcm_sw_params_set_start_threshold(dsp->pcm, swparams, (buffer_size / period_size) * period_size);
	if (err < 0) {
		printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
		goto err;
	}
	// allow the transfer when at least period_size samples can be processed
	err = snd_pcm_sw_params_set_avail_min(dsp->pcm, swparams, period_size);
	if (err < 0) {
		printf("Unable to set avail min for playback: %s\n", snd_strerror(err));
		goto err;
	}
	// align all transfers to 1 sample
	err = snd_pcm_sw_params_set_xfer_align(dsp->pcm, swparams, 1);
	if (err < 0) {
		printf("Unable to set transfer align for playback: %s\n", snd_strerror(err));
		goto err;
	}
	// write the parameters to the playback device
	err = snd_pcm_sw_params(dsp->pcm, swparams);
	if (err < 0) {
		printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
		goto err;
	}                  
*/
    //dbg("snd_pcm_state() = %d %s\n", snd_pcm_state(dsp->pcm), alsa_get_state(dsp));
    if (snd_pcm_state(dsp->pcm) == SND_PCM_STATE_PREPARED){ 
        err = snd_pcm_start(dsp->pcm);
        if (err < 0) {
            log_addf("Unable to start prepared alsa stream: %s", snd_strerror(err));
            goto err;
        }
	}                  
    
    dsp->pcm_recover = 5;
    
    goto x;

err:;
    dbg("alsa not opened\n");
    alsa_close(dsp);
    return -1;
x:;
    //dbg("alsa opened\n");
    return 0;
}


int alsa_close(struct dsp *dsp){
    if (!dsp->pcm_opened) return 0;
    snd_pcm_close(dsp->pcm);
    //dbg("alsa closed (%s)\n", dsp->name);
    dsp->pcm_opened=0;
    return 0;
}


int alsa_write(struct dsp *dsp, void *data, int frames){
    int ret;
    short *sh = (short*)data;

    //dbg("\nalsa_write(%df)    state=%s\n", frames, alsa_get_state(dsp));
    
    while(frames > 0){
        ret = snd_pcm_writei(dsp->pcm, sh, frames);
        //dbg("snd_pcm_writei(%df) returns %d    state=%s\n", frames, ret, alsa_get_state(dsp));
        if (ret < 0) {
            if (ret != -EPIPE) return ret;
            ret = snd_pcm_recover(dsp->pcm, -EPIPE, 0);    /* snd_pcm_recover is since libasound 1.0.11 */
            dbg("snd_pcm_recover() returns %d\n", ret);
            if (ret < 0) return ret;
            ret = 0;
        }
        frames -= ret;
        sh += ret * dsp->channels; 
    }
    return frames;
}


int alsa_read(struct dsp *dsp, void *data, int aframes){
    int frames;

    //dbg("snd_pcm_readi(%d)... state=%s\n", aframes, alsa_get_state(dsp));
repeat:;
    frames = snd_pcm_readi(dsp->pcm, data, aframes);
    //dbg("snd_pcm_readi return %d\n", frames);
    if (frames < 0) {
        if (frames == -EPIPE && dsp->pcm_recover-- > 0){
            dbg("snd_pcm_readi returns %d, trying to recover... %d\n\n", frames, dsp->pcm_recover);
            frames = snd_pcm_recover(dsp->pcm, frames, 0);
            //dbg("snd_pcm_recover returns %d\n", frames);
            if (frames == 0) goto repeat;
            /*if (frames==-EPIPE){
                frames=dsp->bpf*dsp->channels;
                log_addf("Alsa record buffer overrun (%d), forcing value (%d)", -EPIPE, frames);
            }*/
            //frames = 1;
            //log_addf("Alsa record buffer overrun (%d), forcing value (%d)", -EPIPE, frames);
        }
        return -1;
    }else{
        dsp->pcm_recover = 5;
    }
    if (frames > 0 && frames != aframes){
        dbg("snd_pcm_readi(%d) returns %d\n", aframes, frames); 
    }
   // if (frames>0) frames*=dsp->bpf*dsp->channels;
/*    if (frames==-EPIPE){
        frames=dsp->bpf*dsp->channels;
        log_addf("Alsa record buffer overrun (%d), forcing value (%d)", -EPIPE, frames);
    }*/
    return frames;
}


int alsa_reset(struct dsp *dsp){
    snd_pcm_drop(dsp->pcm);
    return 0;
}


int alsa_sync(struct dsp *dsp){
    snd_pcm_drain(dsp->pcm);
    return 0;
}

#ifdef HAVE_SNDFILE
int alsa_set_format(struct dsp *dsp, SF_INFO *sfinfo, int rec){

    switch (sfinfo->format & SF_FORMAT_SUBMASK){
/*        case SF_FORMAT_PCM_U8:
            format=AFMT_U8;
            break;
        case SF_FORMAT_PCM_S8:
            format=AFMT_S8;
            break;*/
        case SF_FORMAT_ULAW:
            dsp->pcm_format=SND_PCM_FORMAT_MU_LAW;
            break;            
        case SF_FORMAT_ALAW:
            dsp->pcm_format=SND_PCM_FORMAT_A_LAW;
            break;            
        case SF_FORMAT_IMA_ADPCM:
            dsp->pcm_format=SND_PCM_FORMAT_IMA_ADPCM;
            break;            
        default:
            dsp->pcm_format=SND_PCM_FORMAT_S16_LE;    
    }
    dsp->channels=sfinfo->channels; 
    dsp->speed=sfinfo->samplerate;   
    return 0;
}
#endif

int alsa_get_sources(struct dsp *dsp, GString *labels){
    int err;
    snd_mixer_t *mixer_handle;
    snd_mixer_elem_t *elem;
    const char *label;

    g_string_truncate(labels, 0);
    if (!dsp || !dsp->alsa_mixer || strlen(dsp->alsa_mixer) == 0) return 0; 

    err=snd_mixer_open(&mixer_handle, 0);
    if (err<0) goto x3;
    err=snd_mixer_attach(mixer_handle, dsp->alsa_mixer);
    if (err<0) goto x3;
    err=snd_mixer_selem_register(mixer_handle, NULL, NULL);
    if (err<0) goto x3;
    err=snd_mixer_load(mixer_handle);
    if (err<0) goto x3;

    for (elem=snd_mixer_first_elem(mixer_handle);
         elem;
         elem=snd_mixer_elem_next(elem)){

        if (!snd_mixer_selem_has_capture_switch(elem)) continue;
              
        label=snd_mixer_selem_get_name(elem);
        if (strcmp(label, "Capture")==0) continue;

        if (strlen(labels->str)>0) g_string_append_c(labels, ';');
        g_string_append(labels, label);
    }
x3:;    
    snd_mixer_close(mixer_handle);
    return 0;
} 

int alsa_set_source(struct dsp *dsp){
    int err;
    snd_mixer_t *mixer_handle;
    snd_mixer_elem_t *elem;
    const char *label;
	        
    //dbg("alsa_set_source('%s')\n", dsp->source);
    if (!dsp->alsa_src || !*dsp->alsa_src){
        return 0;
    }

    err=snd_mixer_open(&mixer_handle, 0);
    if (err<0) goto x3;
    err=snd_mixer_attach(mixer_handle, dsp->alsa_mixer);
    if (err<0) goto x3;
    err=snd_mixer_selem_register(mixer_handle, NULL, NULL);
    if (err<0) goto x3;
    err=snd_mixer_load(mixer_handle);
    if (err<0) goto x3;

    for (elem=snd_mixer_first_elem(mixer_handle);
         elem;
         elem=snd_mixer_elem_next(elem)){

        long min, max, val;

        if (!snd_mixer_selem_has_capture_switch(elem)) continue;
        label=snd_mixer_selem_get_name(elem);
        //dbg("elem label=%s\n", label);
        if (strcmp(label, dsp->alsa_src)!=0 && strcmp(label, "Capture")!=0) continue;

        snd_mixer_selem_set_capture_switch_all(elem, 1);

        snd_mixer_selem_get_capture_volume_range(elem, &min, &max);

        if (dsp->rlev<0) continue;

        val = min + ((max-min+1)*dsp->rlev)/100;
//        dbg("SET elem label=%s capt\t%d-%d %d%%=%d\n", label, min, max, dsp->rlev, val);

        snd_mixer_selem_set_capture_volume_all(elem, val); 
    }
    
    
x3:;    
    snd_mixer_close(mixer_handle);
    return 0;
}

int alsa_set_plevel(struct dsp *dsp){
    int err;
    snd_mixer_t *mixer_handle;
    snd_mixer_elem_t *elem;
    const char *label;

    //dbg("alsa_set_plevel('%s')\n", dsp->alsa_src);
    if (!dsp->alsa_src || !*dsp->alsa_src){
        return 0;
    }

    err=snd_mixer_open(&mixer_handle, 0);
    if (err<0) goto x3;
    err=snd_mixer_attach(mixer_handle, dsp->alsa_mixer);
    if (err<0) goto x3;
    err=snd_mixer_selem_register(mixer_handle, NULL, NULL);
    if (err<0) goto x3;
    err=snd_mixer_load(mixer_handle);
    if (err<0) goto x3;

    for (elem=snd_mixer_first_elem(mixer_handle);
         elem;
         elem=snd_mixer_elem_next(elem)){

        long min, max, val;

        if (!snd_mixer_selem_has_playback_volume(elem)) continue;
        label=snd_mixer_selem_get_name(elem);
//        dbg("elem label=%s\n", label);
        if (strcmp(label, dsp->alsa_src)!=0 && strcmp(label, "Master")!=0) continue;

        snd_mixer_selem_get_playback_volume_range(elem, &min, &max);

        if (dsp->plev<0) continue;

        val = min + ((max-min+1)*dsp->plev)/100;

        snd_mixer_selem_set_playback_volume_all(elem, val); 
    }
    
    
x3:;    
    snd_mixer_close(mixer_handle);
    return 0;
}

char *alsa_get_state(struct dsp *dsp){
    switch (snd_pcm_state(dsp->pcm)){
        case SND_PCM_STATE_OPEN: return "Open";
        case SND_PCM_STATE_SETUP: return "Setup installed";
        case SND_PCM_STATE_PREPARED: return "Ready to start";
        case SND_PCM_STATE_RUNNING: return "Running";
        case SND_PCM_STATE_XRUN: return "Stopped: underrun (playback) or overrun (capture) detected";
        case SND_PCM_STATE_DRAINING: return "Draining: running (playback) or stopped (capture)";
        case SND_PCM_STATE_PAUSED: return "Paused";
        case SND_PCM_STATE_SUSPENDED: return "Hardware is suspended";
        case SND_PCM_STATE_DISCONNECTED: return "Hardware is disconnected";
        default: return "???";
    }
}

int alsa_set_sdr_format(struct dsp *dsp, int frames, int speed, int rec){
    char *name;
    
	if (rec){
		if (!dsp->pcm_rec || !*dsp->pcm_rec) dsp->pcm_rec = g_strdup("default");
        name = dsp->pcm_rec;
	}else{
		if (!dsp->pcm_play || !*dsp->pcm_play) dsp->pcm_play = g_strdup("default");
        name = dsp->pcm_play;
	}

	dsp->speed = speed;
    if (!dsp->speed){
        unsigned int rate_max;
        int dir_max, err;
        snd_pcm_hw_params_t *hwparams;

        dsp->speed = 48000;
    
        err = snd_pcm_open(&dsp->pcm, name, rec ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0);
        if (err < 0) goto xxx;
        
        snd_pcm_hw_params_alloca(&hwparams);
        err = snd_pcm_hw_params_any(dsp->pcm, hwparams);
        if (err < 0) goto xx;

        err = snd_pcm_hw_params_get_rate_max(hwparams, &rate_max, &dir_max);
        if (err < 0) goto xx;

        dsp->speed = rate_max;
        log_addf("alsa set rate_max=%d", rate_max);
        
xx:;
        snd_pcm_close(dsp->pcm);
xxx:;
    }

	dsp->channels = 2;
	dsp->period_time = frames * 1000 / dsp->speed;

	dsp->pcm_format = SND_PCM_FORMAT_S16_LE;
    return 0;

}    

#endif
