/* gcc `pkg-config --cflags --libs alsa` alsa_enum.c -o alsa_enum */

#include <stdbool.h>
#include <alsa/asoundlib.h>

int enum_alsa_cards(void * context, bool (* callback)(int card_no))
{
  int card_no = -1;
  int counter = 0;

  while (snd_card_next(&card_no) >= 0 && card_no >= 0)
  {
    if (!callback(card_no))
    {
      return -1;
    }
    counter++;
  }

  return counter;
}

bool dump_card_pcm(int card_no)
{
  snd_ctl_t * handle;
  snd_ctl_card_info_t * info;
  snd_pcm_info_t * pcminfo_capture;
  snd_pcm_info_t * pcminfo_playback;
  snd_ctl_card_info_alloca(&info);
  snd_pcm_info_alloca(&pcminfo_capture);
  snd_pcm_info_alloca(&pcminfo_playback);
  char card_id[100];
  int device_no;
  bool has_capture;
  bool has_playback;

  sprintf(card_id, "hw:%d", card_no);

  if (snd_ctl_open(&handle, card_id, 0) >= 0 &&
      snd_ctl_card_info(handle, info) >= 0)
  {
    printf("%s\t%s\n", card_id, snd_ctl_card_info_get_name(info));

    device_no = -1;

    while (snd_ctl_pcm_next_device(handle, &device_no) >= 0 && device_no != -1)
    {
      snd_pcm_info_set_device(pcminfo_capture, device_no);
      snd_pcm_info_set_subdevice(pcminfo_capture, 0);
      snd_pcm_info_set_stream(pcminfo_capture, SND_PCM_STREAM_CAPTURE);
      has_capture = snd_ctl_pcm_info(handle, pcminfo_capture) >= 0;

      snd_pcm_info_set_device(pcminfo_playback, device_no);
      snd_pcm_info_set_subdevice(pcminfo_playback, 0);
      snd_pcm_info_set_stream(pcminfo_playback, SND_PCM_STREAM_PLAYBACK);
      has_playback = snd_ctl_pcm_info(handle, pcminfo_playback) >= 0;

      if (has_capture && has_playback)
      {
        printf("%s,%d\t%s (duplex)\n", card_id, device_no, snd_pcm_info_get_name(pcminfo_capture));
      }
      else if (has_capture)
      {
        printf("%s,%d\t%s (capture)\n", card_id, device_no, snd_pcm_info_get_name(pcminfo_capture));
      }
      else if (has_playback)
      {
        printf("%s,%d\t%s (playback)\n", card_id, device_no, snd_pcm_info_get_name(pcminfo_playback));
      }
    }
    snd_ctl_close(handle);
  }

  return true;
}

void dump_pcm(void)
{
  printf("--------- PCM ----------\n");
  printf("(%d cards)\n", enum_alsa_cards(NULL, dump_card_pcm));
}

void dump_raw_midi_device_stream(int card_no, snd_ctl_t * ctl, int device_no, snd_rawmidi_stream_t stream_direction)
{
	snd_rawmidi_info_t * info;
	snd_rawmidi_info_alloca(&info);

  snd_rawmidi_info_set_device(info, device_no);
  snd_rawmidi_info_set_stream(info, stream_direction);
  snd_rawmidi_info_set_subdevice(info, 0);

  if (snd_ctl_rawmidi_info(ctl, info) >= 0)
  {
    int err;
    int sub, nsubs;

    nsubs = snd_rawmidi_info_get_subdevices_count(info);

    for (sub = 0; sub < nsubs; sub++)
    {
      snd_rawmidi_info_set_subdevice(info, sub);
      if (snd_ctl_rawmidi_info(ctl, info) >= 0)
      {
        printf(
          "%3s %2d,%2d,%2d  0x%08X  %s [%s] (%s)\n",
          stream_direction == SND_RAWMIDI_STREAM_INPUT ? "IN" : "OUT",
          card_no,
          device_no,
          sub,
          snd_rawmidi_info_get_flags(info),
          snd_rawmidi_info_get_name(info),
          snd_rawmidi_info_get_subdevice_name(info),
          snd_rawmidi_info_get_id(info));
      }
    }
  }
}

bool dump_card_raw_midi(int card_no)
{
	snd_ctl_t * ctl;
  char card_id[100];
  int device_no = -1;

  snprintf(card_id, sizeof(card_id), "hw:%d", card_no);

  if (snd_ctl_open(&ctl, card_id, SND_CTL_NONBLOCK) >= 0)
  {
    device_no = -1;
    while (snd_ctl_rawmidi_next_device(ctl, &device_no) >= 0 && device_no >=0)
    {
      dump_raw_midi_device_stream(card_no, ctl, device_no, SND_RAWMIDI_STREAM_INPUT);
      dump_raw_midi_device_stream(card_no, ctl, device_no, SND_RAWMIDI_STREAM_OUTPUT);
    }
  }

  return true;
}

void dump_raw_midi(void)
{
  printf("------- Raw MIDI -------\n");
  printf("(%d cards)\n", enum_alsa_cards(NULL, dump_card_raw_midi));
}

int main(void)
{
  dump_pcm();
  dump_raw_midi();
  return 0;
}

