Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник |
Коллекция курьезных вопросов из форумов
KOL и MCK |
KOL и MCK - Компактные программы на Delphi
 
Амкар официальный сайт клубы премьер лиги неофициальный сайт амкар.

"Как работать с микшером?" (Очерк очевидца в одном юните)

  Статья посвящена вопросам по работе с микшером 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.

   Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи:  Вадим Забродин
  

Другие статьи Наверх


  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо