Учебник DirectMusic

Для начала, создайте новый проект в Visual Basic и добавьте в него все необходимое для работы с DirectX. Я подразумеваю, что вы уже знаете, как делать это. Также, я подразумеваю, что вы знакомы хотя бы с разделом DirectDraw. Если же нет, вам будет трудновато усвоить следующий материал. Итак...

Немного теории

DirectMusic - довольно новый раздел в истории DirectX. Он есть пока в версиях 6 и 7. Это неплохой компонент и если машина пользователя обладает достаточными ресурсами, он может быть еще полезнее, чем использование прочих стандартов. DirectMusi загружает .MID - файлы в буфер (наподобии DirectSound/Draw), и, модифицируя их, проигрывает. Кроме MIDI есть еще один формат, поддерживаемый DirectMusic: Downloadable sound. Может быть позже я это освещу, но для среднего игродела на VB это покажется овольно запутанным.

Для этого учебника вам понадобится какой-нибудь .MID файл. Поищите его у себя на диске. Если уж совсем ничего не найдете, вы можете скачать пример и использовать .MID файл, который находится в нем.

 

Переменные

Эти переменные вам надо поместить в раздел Declarations главной формы проекта

Dim dx As New DirectX7 'Главный объект, который имеется в любом приложении DirectX7
Dim perf As DirectMusicPerformance
Dim perf2 As DirectMusicPerformance
'Эти 2 переменные - буфера. "perf" - главный, "perf2" используется чтобы
'Собрать информацию о MIDI файле

Dim seg As DirectMusicSegment
Dim segstate As DirectMusicSegmentState
'Сегменты - содержат сегменты музыки!
Dim loader As DirectMusicLoader
'Это объект, который помогает загрузить файлы в буферы
Public GetStartTime As Long
Public Offset As Long
Public mtTime As Long
Public mtLength As Double
Public dTempo As Double
Dim timesig As DMUS_TIMESIGNATURE
'Содержит подпись времени в MIDI файле
Dim IsPlayingCheck As Boolean
'Простое значение, говорящее проигрывается музыка или нет
Dim msg As String
'Будет использовано при отлове ошибок
Dim time As Double
Dim fIsPaused As Boolean
Dim ISITPAUSED As Boolean
'--ВРЕМЯ-- Эти установки будут использованы в секции разработки прогресс-бара
Dim Total_Time As Double
Dim Current_Time As Double
Dim Percent_Time As Double

Следующий маленький кусочек кода - процедура для генерации сообщения об ошибке. Маленькая, но полезная.

Sub localerror(ErrorNum As Long, ErrorDesc As String) 'Здесь и объяснять-то нечего......
msg = ErrorDesc
msg = "(" & ErrorNum & ") - " & msg
MsgBox msg
End Sub

 

Приготовление интерфейса

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

Обратите внимание, что здесь имеется таймер, и элемент Shape, наложенный на PictureBox. (Эта картинка сделана во время работы программы)

Используйте эту таблицу для выставления свойств обектов:

Form1
.Caption="DirectMusic MIDI Player" -
или что-нибудь в этом роде
.ScaleMode=3 (Pixel)
.StartUpPosition=2 (CenterScreen)

cmdOpen -Верхняя Command Button
.Caption="
Открыть "Tester.Mid" "

cmdPlay
.Caption="
Играть "Tester.Mid" "

cmdPause
.Caption="
Пауза "Tester.Mid" "

cmdStop
.Caption="
Остановить "Tester.Mid" "

Edit_Volume -Метка, показывающая громкость
.Caption=100
.Height=33
.Width=48
.Font=MS Sans Serif, Bold, size 18

UpDown_Volume - Элемент можно найти в "Microsoft Common Controls 2"
.Max=100
.Min=100
.Value=75

Get_Time_Timer -Timer Control
.Enabled=False
.Interval=50

Picture1
.Height=204
.Width=45
.ScaleMode=3 (Pixel)

Shape1 - Поместите это внутри Picture1
.FillStyle=0 (Solid)
.FillColor=Blue (
или по вашему выбору)
.Height=200
.Width=41
.Top=200
.Left=0

lblPercentage -Маленькая метка в центре Progress Bar - кликните на ней правой кнокой и выберите "Bring To Front"
.Caption="%"
.BackStyle=0 (Transparent)
.Alignment=2 (Center)
.Font = MS Sans Serif, Bold, Size 10
.Height=13
.Width=41
.Left=0
.Top=104

lblLength
.AutoSize=true
.Caption="
Длина: "

lblTimeSig
.AutoSize=true
.Caption="
Время: "

lblTempo
.AutoSize=true
.Caption="
Темп: "

Ну вот теперь можно начать писать код. Если у вас возникли трудности с составлением интерфейса, загрузите готовый проект. Ссылка внизу этой страницы.

 

FORM LOAD: Здесь все и начинается

Эти строки - первые, которые выполняет наша программа. Поместите их в модуль кода формы.

Private Sub Form_Load()

On Error GoTo LocalErrors
Set loader = dx.DirectMusicLoaderCreate()
'Это главный компонент DMusic

'Созданием Perf2 мы добиваемся того, что получаеи информацию о сегментах без их
'проигрывания

Set perf2 = dx.DirectMusicPerformanceCreate()
'Создать второй буфер.
Call perf2.Init(Nothing, 0)
perf2.SetPort -1, 80
Call perf2.GetMasterAutoDownload

Set perf = dx.DirectMusicPerformanceCreate()'Создать первый буфер
Call perf.Init(Nothing, 0)
perf.SetPort -1, 80
Call perf.SetMasterAutoDownload(True)
perf.SetMasterVolume (Edit_Volume.Caption * 42 - 3000)
'Простая формула, чтобы послать
'DirectX
правильное значение. Вам надо запомнить это
Edit_Volume.Caption = UpDown_Volume.Value
'Громкость устанавливается в зависимости от
'надписи на edit_Volume. Эта линия переносит зачение с элемента up/down на метку

Exit Sub

LocalErrors:

Call localerror(Err.Number, Err.Description) 'Это мы уже писали

End Sub

Большинство кода должно быть вам понятно. То, что заслуживает комментариев закомментировано :)

 

Открывающий код

В этих строках мы приготавливаем буфера к принятию данных, а затем загружаем в них музыку. Скопируйте этот код в обработку кнопки cmdOpen

Private Sub cmdOpen_Click()

Dim Minutes As Integer
Dim a As Integer
Dim length As Integer
Dim length2 As Integer
Dim FileName As String

On Error GoTo LocalErrors

If Not seg Is Nothing And Not segstate Is Nothing Then ' Cостояния Segment и SegmentState
  If perf.IsPlaying(seg, segstate) = True Then
' сегмент проигрывается, поэтому
      MsgBox "
Пожалуйста, остановите музыку прежде чем выбирать новый сегмент"
      Exit Sub
  ElseIf ISITPAUSED = True Then
      MsgBox "
Пожалуйста, остановите музыку прежде чем выбирать новый сегмент"
    
  'Простой отловщик ошибок
      Exit Sub
  End If
End If

Set loader = Nothing
Set loader = dx.DirectMusicLoaderCreate
FileName = App.path & "\Tester.mid"
'вы можете использовать также
'common dialog control
Set seg = loader.LoadSegment(FileName)
cmdPlay.Enabled = True

' Установть поисковую диреторию на основе размещения загруженного .mid файла
length = Len(FileName)
length2 = length
Dim path As String
Do While path <> "\"
  path = Mid(FileName, length, 1)
  length = length - 1
Loop
Dim SearchDir As String
SearchDir = Left(FileName, length)
loader.SetSearchDirectory (Left(FileName, length + 1))
perf2.SetMasterAutoDownload True

'Сделать все Captions пустыми
lblTempo.Caption = vbNullString
lblTimeSig.Caption = vbNullString
lblLength.Caption = vbNullString

'Проиграть сегмент до тех пор, пока не получим информацию
mtTime = perf2.GetMusicTime()
Call perf2.PlaySegment(seg, 0, mtTime + 2000)

'Получить темп
dTempo = perf2.GetTempo(mtTime + 2000, 0)
lblTempo.Caption = "
Темп: " & Format(dTempo, "00.00")

'Получить временнУю подпись
Call perf2.GetTimeSig(mtTime + 2000, 0, timesig)
lblTimeSig.Caption = "
Время: " & timesig.beatsPerMeasure & "/" & timesig.beat

'Получить длину
mtLength = (((seg.GetLength() / 768) * 60) / dTempo)
'Это важный набор цифр. Здесь вычисляется оставшееся число секунд в файле
Total_Time = mtLength
'Скопируем переменную, чтобы использовать "Total_Time" позже
' Перевести длину в понятное всем временнОе исчисление
Minutes = 0
a = mtLength - 60
Do While a > 0
  Minutes = Minutes + 1
  a = a - 60
Loop
lblLength.Caption = "Длина: " & Format(Minutes, "00") & ":" & Format((mtLength - (Minutes * 60)), "00.0")

' Теперь, получив все данные сегмента мы прекращаем его проигрывание
' Пользователь ничего не услышит, потому что все делалось очень быстро

Call perf2.Stop(seg, Nothing, 0, 0)
seg.SetStandardMidiFile

Exit Sub

LocalErrors:

If Not seg Is Nothing Then
   Call perf2.Stop(seg, Nothing, 0, 0)
'При ошибке прекращаем музыку
End If
MsgBox ("
Проблема при открытии файла.") 'Скажем об ошибке пользователю
FileName = vbNullString

End Sub

Если вы теперь запустите приложение, то увидите, что метки заполнились информацией. Уже полезно, но не будем останавливаться на достигнутом!

 

Проигрывающий код

Давайте теперь получим более весомый результат. Если все сделаем правильно, следующий код будет проигрывать содержимое буфра "perf". Но не пытайтесь запускать программу прежду чем не напишите код команды "stop", так как вы не сможете остановить проигрывание! Скопируйте нижеследующее в обработку кнопки "play".

Private Sub cmdPlay_Click()

If seg Is Nothing Then 'Пользоваель еще не нажимал "Открыть"
  MsgBox ("
Пожалуйста, откройте сегмент или MIDI файл перед проигрыванием ")
  Exit Sub
EndIf

If fIsPaused Then 'Если в режиме паузы, продолжаем проигрывать с этого места
  Offset = mtTime - GetStartTime + Offset + 1
  Call seg.SetStartPoint(Offset)
  Set segstate = perf.PlaySegment(seg, 0, 0)
  cmdPause.BackColor = &H8000000F
'серый -используется как флаг впоследствии
Else
'Перемотка назад, а затем возобновляем проигрывание
  Offset = 0
  If perf.IsPlaying(seg, segstate) = True Then
      Call perf.Stop(seg, segstate, 0, 0)
  End If
  seg.SetStartPoint (0)
  Set segstate = perf.PlaySegment(seg, 0, 0)
  cmdPause.BackColor = &H8000000F
'серый -другой флаг
   '
УСТАНОВИТЬ TIME BAR
  get_Time_Timer.Enabled = True
'активировать прогресс-бар
  Exit Sub
End If

fIsPaused = False

End Sub

 

Код паузы

На очереди код обработки паузы. Вся эта связка (Play/pause/stop) должна быть сделана полностью, поэтому не тестируйте программу, до того как добавите весь код. Следующие строки предназначены для кнопки "Пауза"

Private Sub cmdPause_Click()

On Error GoTo LocalErrors

If seg Is Nothing Then 'Если еще ничего не загружено, уходим
 Exit Sub
End If

IsPlayingCheck = perf.IsPlaying(seg, segstate)
If IsPlayingCheck = True Then
'музыка играет
  fIsPaused = True
  
' приостановить музыку и "нажать" кнопку
  mtTime = perf.GetMusicTime()
  GetStartTime = segstate.GetStartTime()
  Call perf.Stop(seg, Nothing, 0, 0)
  cmdPause.BackColor = &HFFFFC0
'голубой. Этот флаг используется ниже
Else
   If cmdPause.BackColor = &HFFFFC0 Then
'кнопка голубая, значит режим паузы
   
 'выходим из паузы
    fIsPaused = False
    Offset = mtTime - GetStartTime + Offset + 1
    Call seg.SetStartPoint(Offset)
    Set segstate = perf.PlaySegment(seg, 0, 0)
    cmdPause.BackColor = &H8000000F
'серый
  End If
End If
Exit Sub

LocalErrors:

Call localerror(Err.Number, Err.Description) 'Отловщик ошибок

End Sub

 

Код остановки

И наконец, код, останавливающий музыку. Когда вы закончите его писать, то будете уже готовы прослушать первый MIDI файл (заждались уже небось 8))) Эти строки относятся к обработке кнопки "Стоп".

Private Sub cmdStop_Click()

If seg Is Nothing Then 'Еще ничего не загрузили
  Exit Sub
End If

fIsPaused = False 'внутренний флаг, говорящий о том, что нет паузы
cmdPause.BackColor = &H8000000F
'серый
Call perf.Stop(seg, segstate, 0, 0)
cmdPlay.Enabled = True
time = 0
get_Time_Timer.Enabled = False
'Остановить таймер, считающий время

End Sub

 

Изменение громкости

Остаток программы посвящен оптимизации и получения еще больше результатов от приложения. Код, который мы уже написали, является необходимым для каждого проекта, использующего DirectMusic, однако нижеследующий код тоже довольно полезен.
Изменение громкости выполняется очень просто. Вставьте следующие строки в проект:

Private Sub UpDown_Volume_Change()
  Edit_Volume.Caption = UpDown_Volume.Value
'Это копирует значение на метку
  'что вызывает следующую процедуру, которая уже и устанавливает громкость

End Sub

Private Sub Edit_Volume_Change()
  perf.SetMasterVolume (Edit_Volume.Caption * 42 - 3000)
End Sub

Экспериментриуйте с громкостью в ваших программах. Вы можете создавать потрясные эффекты, увеличивая громкость в ключевых местах, или просто плавно снижая ее в конце уровня. Хороший пример - игра Earth Worm Jim 3D :)

 

Индикатор проигрывания

Это последний штрих, который мы добавим в программу. В примере это не показано, но после просмотра этого кода вам должно стать понятно, как выполнить цикл в программе. Этот код выдает прогресс музыки в процентах, так что когда будет 100%, вы останавливаете музыку, перематываете на начало и снова начинаете проигрывать. Получается цикл. Следующие строки обрабатывают событие таймера

Private Sub get_Time_Timer_Timer()

If perf.IsPlaying(Nothing, segstate) = True Then 'Обновлять только, если музыка играет,,,
  Current_Time = ((((perf.GetMusicTime() - (segstate.GetStartTime() - Offset)) / 768) * 60) / dTempo)
  
'получить проигранное время
  Percent_Time = Int((Current_Time / Total_Time) * 100)
'переводим в проценты
  Shape1.Top = 200 - (Percent_Time * 2)
'Так как фигура у нас движется снизу вверх, применяем такой прием
  lblPercentage = Percent_Time & "%"
'скопировать результат на метку
End If

End Sub

Готово!