Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Как работать с микшером

Оформил: DeeCo

Автор: Вадим Забродин

  Статья посвящена вопросам по работе с микшером Windows посредством API. В качестве примера работы предлагается код, выполняющий многие функции стандартного микшера Windows.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes,
  Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls,

  //
  // НЕ ЗАБУДЬТЕ указать mmsystem в uses
  //
  mmsystem;

type
  TFormMixer = class(TForm)
    cb_devs: TComboBox;
    cb_line: TComboBox;
    cb_ctrl: TComboBox;
    Label1: TLabel;
    label_type: TLabel;
    Label2: TLabel;
    label_count: TLabel;
    Label3: TLabel;
    label_bounds: TLabel;
    cb_chg: TCheckBox;
    tb_chg: TTrackBar;
    pb_chg: TProgressBar;
    Timer1: TTimer;
    cbox_chg: TComboBox;
    cb_dest: TComboBox;
    lbl_Error: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure cb_devsChange(Sender: TObject);
    procedure cb_lineChange(Sender: TObject);
    procedure cb_ctrlChange(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure tb_chgChange(Sender: TObject);
    procedure cb_chgClick(Sender: TObject);
    procedure cbox_chgChange(Sender: TObject);
    procedure cb_destChange(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure smchg(var msg: TMessage); message MM_MIXM_CONTROL_CHANGE;
    procedure smchg2(var msg: TMessage); message MM_MIXM_LINE_CHANGE;
  end;

  TMIXERCONTROLDETAILS_BOOLEAN = record
    fValue: longint;
  end;

  TMIXERCONTROLDETAILS_UNSIGNED = record
    dwValue: DWORD;
  end;

var
  FormMixer: TFormMixer;

implementation

{$R *.DFM}
var
  numdevs: integer; // Кол-во устройств (микшеров)
  mixer: HMIXER; // Handle микшера
  mixerlineid: array[0..255] of integer; // Уникальный ID дорожки микшера
  m_ctrl: array[0..255] of TMIXERCONTROL; // Элементы управления дорожки микшера

  ctrl_sel: integer;

procedure TFormMixer.FormCreate(Sender: TObject);
var
  i: integer;
  caps: TMIXERCAPS;
begin
  //
  //   Узнаем кол-во микшеров (т.е. звуковых карт)
  //
  numdevs := mixerGetNumDevs;
  if numdevs = 0 then
  begin
    showmessage('Нет ваших миксеров... а звуковушка у вас есть?');
    exit;
  end;

  //
  //   Через mixerGetDevCaps можно узнать много чего, например версию драйвера,
  // но нам нужно только название микшера
  //

  for i := 0 to numdevs - 1 do
  begin
    mixerGetDevCaps(i, @caps, sizeof(caps));
    cb_devs.Items.Add(caps.szPName);
  end;

  cb_devs.ItemIndex := 0;
  cb_devs.OnChange(self);
end;

procedure TFormMixer.cb_devsChange(Sender: TObject);
var
  caps: TMIXERCAPS;
  line: TMIXERLINE;
  MaxLinecnt: integer;
  i, err: integer;
  maxSrc: integer;
  curDest: integer;
  curSrc: integer;
begin

  //
  //   Чтобы познакомиться поближе с микшером - открываем его. Заодно
  // передаём ему handle нашего окна, для callback'ов (на случай если кто-то
  // еще будет менять установки микшера, например через стандартный микшер винды)
  //
  mixerOpen(@mixer, cb_devs.ItemIndex, handle, 0, CALLBACK_WINDOW);

  //
  //   Опять узнаем способности выбранного микшера
  //

  mixerGetDevCaps(mixer, @caps, sizeof(caps));

  //
  //   Здесь небольшая непонятка для неподготовленного: в mixerGetDevCaps мы подставили
  // handle микшера, хотя всего несколько строк назад мы подставляли его порядковый номер.
  // Это НЕ ошибка. mixerGetDevCaps понимает и то и другое. Кто-то просто хотел облегчить
  // жизнь простому API'шному программисту, но из-за этого возникла путаница.
  //
  // "Хотели как лучше, вышло как всегда" (иногда мне кажется, что это девиз MicroSoft)
  //

  MaxLinecnt := 255;
  cb_dest.Clear;

  //
  //   В микшере дорошки управления делятся на группы по "Направлению".
  // У каждой звуковушки есть, как минимум, два "Направления" (destinations)
  // "Воспроизведение", "Запись". (Их может быть и больше, но это реже).
  //
  //   Для того чтобы узнать какие группы есть у данной звуковушки вызываем mixerGetLineInfo(...
  // Этой функции подставляем handle микшера (Здесь и далее уже не действует порядковый номер),
  // структуру TMIXERLINE, в которой устанавливаем её длину (.cbStruct) и порядковый номер группы
  // (.dwDestination), и устанавливаем нужные флаги.
  //
  // MIXER_GETLINEINFOF_DESTINATION - мы хотим узнать info по группе dwDestination
  // MIXER_OBJECTF_HMIXER           - подставленный handle - микшера.
  //
  //   В результате вызова структура TMIXERLINE заполняется кучей полезных данных, из которых
  // мы берем пока только имя группы (.szname)
  //
  // Повторяем вызов до тех пор, пока mixerGetLineInfo(... не вернет ошибку, что у нее закончились группы.
  //

  line.cbStruct := sizeof(line);

  curDest := -1;
  for i := 0 to maxlinecnt do
  begin
    inc(curDest);
    line.dwDestination := curDest;
    err := mixerGetLineInfo(mixer, @line, MIXER_GETLINEINFOF_DESTINATION or
      MIXER_OBJECTF_HMIXER);
    if err <> 0 then
      break;
    cb_dest.Items.add(line.szname);
  end;

  cb_dest.ItemIndex := 0;
  cb_dest.OnChange(self);
end;

procedure TFormMixer.cb_destChange(Sender: TObject);
var
  caps: TMIXERCAPS;
  line: TMIXERLINE;
  MaxLinecnt: integer;
  i, err: integer;
  maxSrc: integer;
  curDest: integer;
  curSrc: integer;
begin
  curDest := cb_dest.ItemIndex;

  //   Терерь мы хотим узнать какие дорожки есть в выбранной группе
  // (чтобы когда нибудь в оддаленном будущем добраться и до элементов управления дорожек ;) ).
  //
  //   Для этого заново вызываем mixerGetLineInfo(... с просьбой снова предоставить info по выбранной группе
  //

  line.cbStruct := sizeof(line);
  line.dwDestination := curDest;
  err := mixerGetLineInfo(mixer, @line, MIXER_GETLINEINFOF_DESTINATION or
    MIXER_OBJECTF_HMIXER);
  maxSrc := line.cConnections;

  //
  //   В (.cConnections) хранится очень важная для нас информация - кол-во дорожек микшера
  // входящих в данную группу
  //

  cb_line.Clear;
  mixerlineid[0] := line.dwLineID;
  cb_line.Items.add(line.szname);

  //
  //   !Attention! Здесь опять путаница:
  //   Группа дорожек сама является дорожкой микшера (советую перечитать эту фразу еще раз),
  // у которой могут быть свои элементы управления (к которым мы тоже хотим получить доступ).
  // именно поэтому мы добавили в список дорожек cb_line и само название группы дорожек.
  //

  //
  //   Далее практически повторяется цикл по перебору групп микшера. Только теперь
  // (.dwDestination) всегда равен порядковому номеру выбранной группы, а меняется
  // поле (.dwSource) в которое мы подставляем порядковый номер дорожки.
  //
  // Ну и конечно меням флаг на MIXER_GETLINEINFOF_SOURCE
  //

  curSrc := 0;
  for i := 0 to maxSrc - 1 do
  begin
    line.dwDestination := curDest;
    line.dwSource := i;
    err := mixerGetLineInfo(mixer, @line, MIXER_GETLINEINFOF_SOURCE or
      MIXER_OBJECTF_HMIXER);
    if err <> 0 then
      break;
    cb_line.Items.add('-     ' + line.szname);
    mixerlineid[i + 1] := line.dwLineID;
  end;

  //
  //   Касательно массива mixerlineid, который мы заполняем уникальными идентификаторами дорожки (.dwLineID) -
  // это сделано не из природного стремления сохранить под боком побольше информации, эти ID нам еще понадобятся.
  // (Конечно можно каждый раз вызывать mixerGetLineInfo(... и получать эти ID на блюдечке - это тоже ничему не противоречит)
  //

  cb_line.ItemIndex := 0;
  cb_line.OnChange(self);
end;

procedure TFormMixer.cb_lineChange(Sender: TObject);
var
  LineID: integer;
  line: TMIXERLINE;
  ctrl: TMIXERLINECONTROLS;
  i: integer;
begin

  //
  //   Итак, теперь мы хотим узнать какие элементы управления есть у выбранной дорожки микшера.
  //

  cb_ctrl.Clear;
  LineId := mixerlineid[cb_line.ItemIndex]; // Вспоминаем ID дорожки

  //
  //   И опять пользуемся вездесущей mixerGetLineInfo(... !!!
  //
  //   Только теперь мы устанавливаем поле (.dwLineID) и ставим флажок
  // MIXER_GETLINEINFOF_LINEID
  //

  line.cbStruct := sizeof(line);
  line.dwLineID := LineID;
  mixerGetLineInfo(mixer, @line, MIXER_GETLINEINFOF_LINEID);

  //
  //   Из этого вызова мы выносим важную величину - (.cControls),
  // она рассказывает нам сколько элементов управления у данной дорожки
  //

  if line.cControls = 0 then
    exit;

  //
  //   Теперь нам понадобится новая структура TMIXERLINECONTROLS, которя является
  // заголовком для массива структур TMIXERCONTROL (то есть самих элементов управления)
  //
  //   В TMIXERLINECONTROLS устанавливаем следующие св-ва:
  //
  // cbStruct  - размер TMIXERLINECONTROLS
  // cbmxctrl  - размер одного TMIXERCONTROL
  // dwLineID  - ID дорожки для которой мы хотим узнать список контролей
  // cControls - кол-во элементов привязанных к данной дорожке
  //      (Важно установить его правильно, иначе мы получим элементов меньше чем надо или получим ошибку)
  // pamxctrl  - указатель на массив TMIXERCONTROL
  //

  ctrl.cbStruct := sizeof(ctrl);
  ctrl.cbmxctrl := sizeof(TMIXERCONTROL);
  ctrl.dwLineID := LineID;
  ctrl.cControls := line.cControls;
  ctrl.pamxctrl := @m_ctrl;

  //
  //   После заполнения структуры вызываем mixerGetLineControls(... с флажком MIXER_GETLINECONTROLSF_ALL
  // (для других целей можно использовать другие флаги, они описаны в SDK по mixerGetLineControls, но мы сейчас
  // пишем микшер, так что нам надо все).
  //
  //   После вызова mixerGetLineControls заполняем cb_ctrl именами элементов управления
  //

  mixerGetLineControls(mixer, @ctrl, MIXER_GETLINECONTROLSF_ALL);

  for i := 0 to ctrl.cControls - 1 do
  begin
    cb_ctrl.Items.Add(m_ctrl[i].szName);
  end;

  cb_ctrl.ItemIndex := 0;
  cb_ctrl.OnChange(self);
end;

procedure TFormMixer.cb_ctrlChange(Sender: TObject);
var
  txt: string;
  i, err: integer;
  mxc: TMIXERCONTROLDETAILS;
  mxcd: TMIXERCONTROLDETAILS_UNSIGNED;
  mxcl: array[0..100] of integer; //TMIXERCONTROLDETAILS_UNSIGNED;
begin
  lbl_error.visible := false;
  cb_chg.visible := false;
  tb_chg.visible := false;
  pb_chg.visible := false;
  cbox_chg.visible := false;
  timer1.Enabled := false;

  ctrl_sel := cb_ctrl.itemindex;

  //
  //   Перед началом работы с контролем дорожкой микшера проверим состоит ли
  // она из одного item'а или нескольких (.cMultipleItems)...
  //
  //   ДА! Бывает и такое. (Например в SDK сказано, что так и будет выглядеть эквалайзер встроенный
  // в микшер. Сам эквалайзер будет одним элементом управления с item'ами для каждой полосы частот)
  //
  //   Конечно, можно сойти с ума обрабатывая такие вещи, поэтому в данном примере мы ограничимся
  // одним случаем "MultipleItem'овсти".
  //

  if m_ctrl[ctrl_sel].cMultipleItems = 0 then
  begin

    //
    //   Для выяснения подробностей элемента управления нам понадобится структура TMIXERCONTROLDETAILS,
    // которая потребует следующего заполнения:
    //
    // cbStruct    - размер TMIXERCONTROLDETAILS
    // dwControlID - ID элемента управления (получен нами с помощью mixerGetLineControls )
    // cChannels   - кол-во каналов с которыми работает контроль
    //
    //    (вообще говоря может быть 2,3,4 и т.д. Так что установка здесь 1 есть некий произвол, правда система
    //     обычно пытается его обойти: если каналов 2 а мы требуем 1, то обработка ведется как будто бы канал один
    //     и чтение/запись применяются сразу к двум каналам)
    //
    //    Например: "Громкость" какой-то дорожки обычно два канала (левый и правый), но в данном примере мы ставим
    //              cChannels = 1 и управляем громкостью сразу двух каналов (баланс, конечно, теряется, но это пример,
    //              а не коммерческое приложение ;) )
    //
    //
    // cbDetails   - размер значения элемента управления (TMIXERCONTROLDETAILS_UNSIGNED)
    //
    //    (тоже некий произвол... т.к. в общем случае размер не равен 4 байтам)
    //
    // paDetails   - указатель на структуру TMIXERCONTROLDETAILS_UNSIGNED
    //

    mxc.cbStruct := sizeof(mxc);
    mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
    MXC.cMultipleItems := 0;
    mxc.cChannels := 1;
    mxc.cbDetails := sizeof(mxcd);
    mxc.paDetails := @mxcd;

    err := mixerGetControlDetails(mixer, @mxc, 0);

    if err <> 0 then
    begin
      lbl_error.visible := true;
      exit;
    end;
    //
    //   После вызова mixerGetControlDetails заполняется структура mxc и mxcd (если все правильно)
    // и в mxcd.dwValue лежит значение элемента управления
    //

    label_count.caption := inttostr(mxcd.dwValue);
  end
  else
  begin
    //
    //   Теперь более сложный случай - много item'ов внутри элемента управления.
    //
    //   Ограничемся одним случаем (который правда есть почти в каждой звуковой карте).
    // Этот случай - источник звука при записи. Хотя в стандартном микшере это выглядит
    // как набор CheckBox'ов у каждой дорожки микшера, на самом деле это сложный элемент управления:
    // бинарный массив (0-1) прикрепленный к группе "Запись".
    //
    //   Далее показано как обработать его. Поступаем практически также, как и в случае с одиночным эл.управления.
    //
    //   Отличия:
    //
    // cMultipleItems - кол-во значений в элементе
    // cbDetails      - размер массива значений (TMIXERCONTROLDETAILS_UNSIGNED)
    // paDetails      - указатель на массив
    //

    mxc.cbStruct := sizeof(mxc);
    mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
    MXC.cMultipleItems := m_ctrl[ctrl_sel].cMultipleItems;
    mxc.cChannels := 1;
    mxc.cbDetails := sizeof(mxcl);
    mxc.paDetails := @mxcl;

    err := mixerGetControlDetails(mixer, @mxc, 0);
    if err <> 0 then
    begin
      lbl_error.visible := true;
      exit;
    end;

    //
    //   Т.к. мы рассматриваем случай источника звука, а в 99% карточек не может быть выбрано более одного источника,
    // то ищем первое ненулевое значение и делаем вид, что мы считали один mxcd и в нем лежит номер дорожки-источника звука.
    //
    //   (Произвол? Произвол, но работает на всех картах побывавших в моих руках от ESS866 до SB Audigy Platinum)
    //

    for i := 0 to MXC.cMultipleItems - 1 do
      if mxcl[i] <> 0 then
      begin
        label_count.caption := inttostr(i);
        mxcd.dwValue := i;
        break;
      end;
  end;

  //
  //   Теперь, когда мы знаем, чему равно значение элемента управления, неплохо бы узнать
  // какой это элемент и чему может быть равно это значение
  //
  //   Для этого смотрим значение dwControlType. Для того чтобы не разбираться со всеми вариантами
  // мы накладываем маску MIXERCONTROL_CT_CLASS_MASK и у нас остается только класс элемента управления
  //
  // (Для любителей разбираться: см. Windows SDK статью "MIXERCONTROL")
  //

  case (m_ctrl[ctrl_sel].dwControlType and MIXERCONTROL_CT_CLASS_MASK) of
    MIXERCONTROL_CT_CLASS_CUSTOM:
      //
      // Тут все ясно и без объяснений: Загадочный тип, его мы разбирать не будем
      //
      txt := 'Дополнительный';
    MIXERCONTROL_CT_CLASS_FADER:
      //
      // Fader, он же регулятор. Практически все ползунки громкости - Fader'ы
      //
      begin
        txt := 'Регулятор';
        tb_chg.visible := true;
        tb_chg.position := (mxcd.dwValue);
      end;
    MIXERCONTROL_CT_CLASS_LIST:
      //
      //   Список значений... Мы договорились, что у нас только один список - источник звука и в нем
      // установлено только одно значение.
      //
      begin
        txt := 'Список';
        cbox_chg.visible := true;
        cbox_chg.Items.Clear;
        for i := 1 to MXC.cMultipleItems do
        begin
          cbox_chg.Items.Add(cb_line.Items.Strings[i + cb_line.ItemIndex]);
        end;
        cbox_chg.ItemIndex := mxcd.dwValue;
      end;
    MIXERCONTROL_CT_CLASS_METER:
      //
      //   Индикатор. На некоторых картах есть индикатор уровня громкости при Master Volume или Microphone
      //
      //   Из-за того, что индикатор имеет свойство постоянно меняться, но при этом не возникает Message об изменении,
      // в программу добавлен таймер, который запускается как только мы выбираем Meter.
      //
      begin
        txt := 'Индикатор';
        pb_chg.visible := true;
        pb_chg.position := (mxcd.dwValue);
        timer1.Enabled := true;
      end;
    MIXERCONTROL_CT_CLASS_NUMBER:
      //
      //   Какое-то число... в своей работе не встречал
      //
      begin
        txt := 'Число';
        pb_chg.visible := true;
        pb_chg.position := (mxcd.dwValue);
      end;
    MIXERCONTROL_CT_CLASS_SLIDER:
      //
      //   Slider... Принципиальное отличие от Fader'а не улавливаю...
      //
      begin
        txt := 'Движок';
        tb_chg.visible := true;
        tb_chg.position := (mxcd.dwValue);
      end;
    MIXERCONTROL_CT_CLASS_SWITCH:
      //
      //   Switch, он же CheckBox (0 или 1)
      //
      begin
        txt := 'Переключатель';
        cb_chg.visible := true;
        cb_chg.checked := (mxcd.dwValue = 1);
      end;
    MIXERCONTROL_CT_CLASS_TIME:
      //
      //   Время... время чего - я так и не понял...
      //
      begin
        txt := 'Время';
        pb_chg.visible := true;
        pb_chg.position := (mxcd.dwValue);
      end;
  end;
  label_type.caption := txt;

  //
  // Границы изменения значения элемента управления
  //
  label_bounds.Caption :=
    inttostr(m_ctrl[ctrl_sel].bounds.dwMinimum) + '-' +
    inttostr(m_ctrl[ctrl_sel].bounds.dwMaximum);
end;

//
//   Это CallBack процедуры. Необходимость в них возникает, если кто-то управляет микшером
// с другой программы
//

procedure TFormMixer.smchg(var msg: TMessage);
begin
  cb_ctrl.OnChange(self);
end;

procedure TFormMixer.smchg2(var msg: TMessage);
begin
  cb_ctrl.OnChange(self);
end;

//
//   Timer нужен для отрисовки текущего положения индикатора.
//
//   Алгоритм работы ничем не отличается от cb_ctrlChange. Только теперь мы знаем на что смотреть,
// это значительно упрощает процедуру.
//

procedure TFormMixer.Timer1Timer(Sender: TObject);
var
  mxc: TMIXERCONTROLDETAILS;
  mxcd: TMIXERCONTROLDETAILS_UNSIGNED;
  err: integer;
begin
  mxc.cbStruct := sizeof(mxc);
  mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
  MXC.cMultipleItems := 0;
  mxc.cChannels := 1;
  mxc.cbDetails := sizeof(mxcd);
  mxc.paDetails := @mxcd;

  err := mixerGetControlDetails(mixer, @mxc, 0);
  if err <> 0 then
  begin
    lbl_error.visible := true;
    exit;
  end;
  label_count.caption := inttostr(mxcd.dwValue);
  pb_chg.position := (mxcd.dwValue);
end;

//
// Далее идут обработчики Event'ов изменения положения регуляторов
//
// Алгоритмы работы ничем не отличаются от алгоритма чтания cb_ctrlChange и Timer1Timer
//
// Единственное отличие: вместо mixerGetControlDetails используем mixerSetControlDetails
//
// Ну и конечно в mxcd записываем новое значение элемента
//

procedure TFormMixer.tb_chgChange(Sender: TObject);
var
  mxc: TMIXERCONTROLDETAILS;
  mxcd: TMIXERCONTROLDETAILS_UNSIGNED;
  err: integer;
begin
  mxc.cbStruct := sizeof(mxc);
  mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
  MXC.cMultipleItems := 0;
  mxc.cChannels := 1;
  mxc.cbDetails := sizeof(mxcd);
  mxc.paDetails := @mxcd;

  mxcd.dwValue := tb_chg.Position;

  err := mixerSetControlDetails(mixer, @mxc, 0);
  if err <> 0 then
  begin
    lbl_error.visible := true;
    exit;
  end;
  label_count.caption := inttostr(mxcd.dwValue);
end;

procedure TFormMixer.cb_chgClick(Sender: TObject);
var
  mxc: TMIXERCONTROLDETAILS;
  mxcd: TMIXERCONTROLDETAILS_UNSIGNED;
  err: integer;
begin
  mxc.cbStruct := sizeof(mxc);
  mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
  MXC.cMultipleItems := 0;
  mxc.cChannels := 1;
  mxc.cbDetails := sizeof(mxcd);
  mxc.paDetails := @mxcd;

  if cb_chg.checked then
    mxcd.dwValue := 1
  else
    mxcd.dwValue := 0;

  err := mixerSetControlDetails(mixer, @mxc, 0);
  if err <> 0 then
  begin
    lbl_error.visible := true;
    exit;
  end;
  label_count.caption := inttostr(mxcd.dwValue);
  pb_chg.position := (mxcd.dwValue);
end;

procedure TFormMixer.cbox_chgChange(Sender: TObject);
var
  i: integer;
  mxc: TMIXERCONTROLDETAILS;
  mxcl: array[0..100] of integer; //TMIXERCONTROLDETAILS_UNSIGNED;
  err: integer;
begin
  for i := 0 to m_ctrl[ctrl_sel].cMultipleItems - 1 do
  begin
    if i = cbox_chg.ItemIndex then
      mxcl[i] := 1
    else
      mxcl[i] := 0;
  end;

  mxc.cbStruct := sizeof(mxc);
  mxc.dwControlID := m_ctrl[ctrl_sel].dwControlID;
  MXC.cMultipleItems := m_ctrl[ctrl_sel].cMultipleItems;
  mxc.cChannels := 1;
  mxc.cbDetails := sizeof(mxcl);
  mxc.paDetails := @mxcl;

  err := mixerSetControlDetails(mixer, @mxc, 0);
  if err <> 0 then
  begin
    lbl_error.visible := true;
    exit;
  end;
end;

end.
{ ------ Заключение ------ }

  Итак, я надеюсь, что в результате разбора данного кода становится понятна структура и принцип работы с микшером через WinAPI.

Проект Delphi World © Выпуск 2002 - 2024
Автор проекта: USU Software
Вы можете выкупить этот проект.