Проигрывание мелодий из звуковой схемы Windows.
Вы замечали как в некоторых программах при открытии какого-либо диалога начинала играть музыка из звуковой схемы Windows, а при закрытии останавливалась? В данной статье будет рассмотрено, как это можно правильно реализовать в Delphi без излишнего пожирания ресурсов компьютера и без увеличения размеров программы.

Как-то в одной из программ я обнаружил очень интересный эффект - при открытии диалогового окна "О программе…" начинала играть мелодия из звуковой схемы, которая обычно проигрывается при запуске Windows. Мне стало интересно, и я по самую голову закапался в файле справки Delphi, чтобы найти, как это можно реализовать. Единственное, что мне удалось найти, так это функцию MessageBeep. К сожалению, с ее помощью можно воспроизвести только несколько звуков из схемы.

Так как практически всё Windows хранит в реестре, я решил не останавливаться на достигнутом, а интенсивно покопаться в последнем. Через некоторое время мне все же удалось обнаружить мелодии звуковой схемы. Они скрывались в ключе:

HKEY_USERS\.DEFAULT\AppEvents\Schemes\Apps\.Default

Ключ реестра, скрывающий звуковые схемы
Рис.1. Ключ реестра, скрывающий звуковые схемы.

Если обратить внимание на подкаталоги данного ключа, то можно прочитать названия: Maximize, Minimize, SystemQuestion, SystemStart и другие. Как вы уже успели заметить по названиям, это и есть различные события из звуковой схемы. Каждый такой подкаталог содержит еще две папочки -.Current (текущая звуковая схема), и.Default (звуковая схема по умолчанию). Обе они в качестве параметров по умолчанию имеют путь к звуковому файлу. Это хорошо видно на рисунке 1. Я нашел, то, что искал, но что делать дальше? Как связать реестр и воспроизведение звуков? Немного подумав, я решил сделать это следующим образом: нужно прочитать значение, то есть путь к звуковому файлу, а потом воспроизвести его. Для этого была написана вот такая процедура:

//Проигрывание стандартного звука
function PlaySystemSound(KeyName: String): BOOL;
var
 Key   : HKEY;
 Size  : DWORD;
 WaveFile: String;
begin
 if RegOpenKeyEx(HKEY_USERS, PChar('.DEFAULT\AppEvents\Schemes\Apps\.Default\' + 
  KeyName), 0, KEY_QUERY_VALUE,Key) = ERROR_SUCCESS then
  begin
   if RegQueryValueEx(Key, nil, nil, nil, nil, @Size) = ERROR_SUCCESS then
   begin
    if Size > 1 then
    begin
     SetLength(WaveFile, Size);
      if RegQueryValueEx(Key, nil, nil, nil,
       PByte(PChar(WaveFile)), @Size) = ERROR_SUCCESS then
        SetLength(WaveFile, Size-1)
        else WaveFile := '';
     end;
   end;
   RegCloseKey(Key);
 end;
  if IsWin9x //Определяем платформу
   then Result := sndPlaySound(PChar(WaveFile), SND_ASYNC)
   else Result := sndPlaySound(PChar(ExpandEnvironment(WaveFile)), SND_ASYNC);
end;

Заметьте, что я не использовал визуальный модуль для работы с реестром Registry, а создал всё с помощью API для облегчения веса программы в килобайтах. Зачем нам лишний вес, если процедура будет выполнять такую, на мой взляд, глупую операцию как воспроизведение звуков из схемы Windows? Я не считаю, что украшательства требуют жертв.

В качестве параметра KeyName процедуры могут выступать такие значения:

SystemStart\.current - Запуск Windows;
.Default\.current - Стандартный звук;
SystemExit\.current - Выход из Windows;
SystemHand\.current - Критическая ошибка;
SystemQuestion\.current - Вопрос;
SystemExclamation\.current - Восклицание;
SystemAsterisk\.current - Звездочка;
Close\.current - Закрытие программы;
Open\.current - Открытие программы;
Maximize\.current - Развертывание;
Minimize\.current - Свертывание;
RestoreDown\.current - Восстановление окна с полного экрана;
RestoreUp\.current - В окно из значка;
AppGPFault\.current - Ошибка программы;
MenuCommand\.current - Команда меню;
MenuPopup\.current - Всплывающее меню;
MailBeep\.current - Уведомление о приходе почты;
CCSelect\.current - Выделить;
ShowBand\.current - Отображение панели инструментов.

Например, воспроизведение мелодии "Запуск Windows" будет выглядеть так:

PlaySystemSound('ShowBand\.current');

Чтобы остановить воспроизведение мелодии, достаточно запустить функцию sndPlaySound с параметром nil:

sndPlaySound(nil, SND_ASYNC);

Единственно, что вам нужно сделать для нормальной работы процедуры, так это подключить модуль MMsystem (в разделе Uses), в котором прописана функция sndPlaySound. Но можно пойти и другим путем, что я незамедлительно и сделал. Для этого я создал специальный модуль, в котором прописал функцию sndPlaySound, просто изъяв её из модуля MMsystem. Ниже приводиться полный исходник моего модуля. Заметьте, что он использует всего лишь один модуль Windows.

unit Lenin_Playsnd;

(***************************************)
(*  LENIN INC                          *)
(*  Online:  http://www.lenininc.com/  *)
(*  Free for non commercial use.       *)
(***************************************)

interface

uses
 Windows;

//Проигрывание мелодии. Пример: PlaySystemSound(pwStandart);
function PlaySystemSound(KeyName: String): BOOL;
//Остановка проигрывания мелодии
function StopSystemSound: BOOL;

{ Функции проигрывания Wave-файлов (из MMsystem) }
function sndPlaySoundA(lpszSoundName: PAnsiChar; uFlags: UINT): BOOL; stdcall;
function sndPlaySoundW(lpszSoundName: PWideChar; uFlags: UINT): BOOL; stdcall;
function sndPlaySound(lpszSoundName: PChar; uFlags: UINT): BOOL; stdcall;

const
 pwStartWindows = 'SystemStart\.current';       //Запуск Windows
 pwStandart     = '.Default\.current';          //Стандартный звук
 pwExitWindows  = 'SystemExit\.current';        //Выход из Windows
 pwError        = 'SystemHand\.current';        //Критическа& ошибка
 pwQuestion     = 'SystemQuestion\.current';    //Вопрос
 pwExclamation  = 'SystemExclamation\.current'; //Восклицание
 pwAsterisk     = 'SystemAsterisk\.current';    //Звездочка
 pwCloseApp     = 'Close\.current';             //Закрытие программы
 pwOpenApp      = 'Open\.current';              //Открытие программы
 pwMaximize     = 'Maximize\.current';          //Развертывание
 pwMinimize     = 'Minimize\.current';          //Свертывание
 pwRestoreDown  = 'RestoreDown\.current';       //Восстановление окна с полного экрана
 pwRestoreUp    = 'RestoreUp\.current';         //В окно из значка
 pwFaultApp     = 'AppGPFault\.current';        //Ошибка программы
 pwMenuCommand  = 'MenuCommand\.current';       //Команда меню
 pwMenuPopup    = 'MenuPopup\.current';         //Всплывающее меню
 pwMail         = 'MailBeep\.current';          //Уведомление о приходе почты
 pwSelect       = 'CCSelect\.current';          //Выделить
 pwToolBar      = 'ShowBand\.current';          //Отображение панели инструментов

const
 SND_ASYNC = $0001;  { play asynchronously }

implementation

const
 mmsyst = 'winmm.dll';

function sndPlaySoundA; external mmsyst name 'sndPlaySoundA';
function sndPlaySoundW; external mmsyst name 'sndPlaySoundW';
function sndPlaySound; external mmsyst name 'sndPlaySoundA';

//Определяем платформу
function IsWin9x: Bool; {True = Win9x} { False = NT}
asm
 xor eax, eax
 mov ecx, cs
 xor cl, cl
 jecxz @@quit
 inc eax
 @@quit:
end;

//Пример: ExpandEnvironment('%SystemRoot%');
function ExpandEnvironment(const sDir: String): String;
var
 buf: array [0..1023] of Char;
begin
 ExpandEnvironmentStrings(PChar(sDir), buf, SizeOf(buf));
 Result := buf;
// if (Result<>'') and (Result[Length(Result)]<>'\') then Result:=Result+'\';
end;

//Проигрывание стандартного звука
function PlaySystemSound(KeyName: String): BOOL;
var
 Key   : HKEY;
 Size  : DWORD;
 WaveFile: String;
begin
 if RegOpenKeyEx(HKEY_USERS, PChar('.DEFAULT\AppEvents\Schemes\Apps\.Default\' + 
  KeyName), 0, KEY_QUERY_VALUE,Key) = ERROR_SUCCESS then
  begin
   if RegQueryValueEx(Key, nil, nil, nil, nil, @Size) = ERROR_SUCCESS then
   begin
    if Size > 1 then
    begin
     SetLength(WaveFile, Size);
      if RegQueryValueEx(Key, nil, nil, nil,
       PByte(PChar(WaveFile)), @Size) = ERROR_SUCCESS then
        SetLength(WaveFile, Size-1)
        else WaveFile := '';
     end;
   end;
   RegCloseKey(Key);
 end;
  if IsWin9x //Определяем платформу
   then Result := sndPlaySound(PChar(WaveFile), SND_ASYNC)
   else Result := sndPlaySound(PChar(ExpandEnvironment(WaveFile)), SND_ASYNC);
end;

//Остановка проигрывания звука
function StopSystemSound: BOOL;
begin
 Result := sndPlaySound(nil, SND_ASYNC);
end;

end.

Данный модуль не занимает много места в программе, а функции и процедуры нормально работаю во всех операционных средах Windows.

Теперь вы можете озвучить в своем приложении появление диалогового окна "О программе…". При этом если у пользователя не установлена звуковая схема, он не увидит никаких ошибок по этому поводу и даже не будет знать, что при открытии какого-то диалога воспроизводиться звук. Зачем пугать и путать пользователя лишними сообщениями об ошибках? Кстати, в Windows XP даже при выключенной звуковой схеме мелодия будет проигрыватся.

Полностью уже написанный модуль, а также и массу других не менее интересных модулей вы можете найти на моем сайте в разделе WIN32API. Все они распространяются совершенно бесплатно и предназначены для написания программ на чистом WIN32API, то есть шустрых, маленьких размером приложений без использования визуальных компонентов.

Copyright © 2002-2008 by Vladimir Drigalkin aka LENIN INC. All Rights Reserved.