前回は素のwaveファイルお再生しました。
これを使えばほかのものでも応用ができます。

今回はmp3をやります。
mp3はデコーダやプレイやがあって今さらですけど、
ABでもできるんですよ。

今回使うのは定番のmpglib.dllです。
mpglib.dllのある場所http://mpesch3.de1.cc/misc.html
こいつはABから使うことを想定していないしドキュメントも不親切です
だから元となったmpg123というライブラリも調べてみるといいかもしれませんね。

まずこのDLLは3つの関数しかないので、簡単ですけど、便利な昨日がないので
自分で作ります。
ID3V2も自力で読み飛ばさないといけません。

abmpglib.abp


Const AUDIOBUFSIZE = 16384

Const SBLIMIT = 32
Const SSLIMIT = 18
Const SCALE_BLOCK = 12 /* Layer 2 */

Const MPG_MD_STEREO = 0
Const MPG_MD_JOINT_STEREO = 1
Const MPG_MD_DUAL_CHANNEL = 2
Const MPG_MD_MONO = 3

Const MAXFRAMESIZE = 1792

Const MP3_ERR = -1
Const MP3_OK = 0
Const MP3_NEED_MORE = 1

Type al_table
bits As Word
d As Word
End Type

Type frame
stereo As Long
jsbound As Long
single As Long
lsf As Long
mpeg25 As Long
header_change As Long
lay As Long
error_protection As Long
bitrate_index As Long
sampling_frequency As Long
padding As Long
extension As Long
mode As Long
mode_ext As Long
copyright As Long
original As Long
emphasis As Long
framesize As Long /* computed framesize */
II_sblimit As Long /* Layer 2 */
alloc As *al_table /* Layer 2 */
do_layer As VoidPtr /* Layer 2 */
End Type

Type mpglib_buf
pnt As *Byte
size As Long
pos As Long
pnext As *mpglib_buf
prev As *mpglib_buf
End Type

Type framebuf
buf As *mpglib_buf
pos As Long
pnext As *frame
prev As *frame
End Type

Type mpstr
head As *mpglib_buf
tail As *mpglib_buf

bsize As Long
framesize As Long
fsizeold As Long
fr As frame
bsspace[ELM(2*(MAXFRAMESIZE+512))] As Byte /* MAXFRAMESIZE */
hybrid_block[ELM(2*2*SBLIMIT*SSLIMIT)] As Single
hybrid_blc[ELM(2)] As Long
header As DWord
bsnum As Long
synth_buffs[ELM(2*2*&h110)] As Single
synth_bo As Long
End Type

Type ID3V2
ID3[2] As Byte
majorversion As Byte
revision As Byte
flag As Byte
size[3] As Byte
End Type

Declare Function InitMP3 CDECL Lib"mpglib.dll" Alias "_InitMP3" (mp As *mpstr) As Long
Declare Sub ExitMP3 CDECL Lib"mpglib.dll" Alias "_ExitMP3" (mp As *mpstr)
Declare Function decodeMP3 CDECL Lib"mpglib.dll" Alias "_decodeMP3" (mp As *mpstr, inmemory As *Byte, inmemsize As Long, outmemory As *Byte, outmemsize As Long, done As *Long) As Long

Dim br_tbl[1,14] = [
[ 0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320],
[ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160]
] As Long

Dim rate_tbl[9] = [44100, 48000, 32000, 22050, 24000, 16000 , 11025 , 12000 , 8000, 0] As Long

'ID3V2のサイズを詰める
Function unpack_sint28 (ptr As *Byte) As DWord
Dim value As DWord

If (ptr[0] And &h80) Then Exit Function

value = value Or (ptr[0] And &h7f)
value = (value << 7) Or (ptr[1] And &h7f)
value = (value << 7) Or (ptr[2] And &h7f)
value = (value << 7) Or (ptr[3] And &h7f)

unpack_sint28 = value
End Function


Type ABMP3DEC_CTX
mp As mpstr
buf[AUDIOBUFSIZE] As Byte
hF As HANDLE
rate As Long
ch As Long
pcmb As DWord
startpos As DWord
more As Long
status As Long
End Type

Dim mc As ABMP3DEC_CTX

'mp3ファイルを開いてデコード準備
Function OpenMp3(mp3file As *Byte) As Long
Dim size As Long
Dim out[8192] As Byte
Dim len As Long
Dim ret As Long
Dim id3 As ID3V2

If mc.status = TRUE Then Exit Function

ZeroMemory(VarPtr(mc), SizeOf(ABMP3DEC_CTX))

mc.hF = CreateFile(mp3file, GENERIC_READ, FILE_SHARE_READ, ByVal 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If mc.hF = INVALID_HANDLE_VALUE Then Exit Function

'ID3V2
ReadFile(mc.hF, VarPtr(id3), SizeOf(ID3V2), VarPtr(len), ByVal 0)
If strstr(id3.ID3, "ID3") Then
If Not (id3.size[0] And &h80) Then mc.startpos = unpack_sint28(id3.size)+10
End If
SetFilePointer(mc.hF, mc.startpos, 0, FILE_BEGIN)

InitMP3(VarPtr(mc.mp))

'一度読み込んでフォーマットを取得
If ReadFile(mc.hF, mc.buf, 1024, VarPtr(len), ByVal 0) = FALSE Or len <= 0 Then
'読み込みエラー
End If
ret = decodeMP3(VarPtr(mc.mp), mc.buf, len, out, 8192, VarPtr(size))
If ret <> MP3_OK Then
'デコードエラー
End If

mc.rate = rate_tbl[mc.mp.fr.sampling_frequency]
mc.ch = mc.mp.fr.stereo

'リセット
SetFilePointer(mc.hF, mc.startpos, 0, FILE_BEGIN)
ExitMP3(VarPtr(mc.mp))
InitMP3(VarPtr(mc.mp))

mc.status = TRUE
OpenMp3 = TRUE
End Function

'サンプリングレート、チャンネル数を得る
Function GetFormat(rate As *Long, ch As *Long) As Long
If mc.status = FALSE Then Exit Function

SetDWord(rate, mc.rate)
SetDWord(ch, mc.ch)
End Function

'デコードし、そのサイズを返す。
Function ReadMp3(pcm As *Byte, buflen As Long) As DWord
Dim pos As DWord
Dim len As DWord
Dim siz As DWord
Dim ret As Long
If mc.status = FALSE Then Exit Function

pos = 0
siz = 0
Do
If mc.more = FALSE Then
ret = ReadFile(mc.hF, mc.buf, 16384, VarPtr(len), ByVal 0)
If ret = FALSE Or len = 0 Then *JUMP
ret = decodeMP3(VarPtr(mc.mp), mc.buf, len, VarPtr(pcm[pos]), 8192, VarPtr(siz))
If ret = MP3_OK Then mc.more = TRUE
End If

While (ret = MP3_OK)
pos = pos + siz
If pos+8192 > buflen Then *JUMP
ret = decodeMP3(VarPtr(mc.mp), NULL, 0, VarPtr(pcm[pos]), 8192, VarPtr(siz))
Wend

mc.more = FALSE'次に読み込みが必要

Loop While pos+8192 < buflen

*JUMP
ReadMp3 = pos
mc.pcmb = mc.pcmb + pos
End Function

'終わり
Function CloseMp3() As Long
If mc.status = FALSE Then Exit Function

CloseHandle(mc.hF)
ExitMP3(VarPtr(mc.mp))
mc.status = FALSE
End Function

つぎに再生部

#include <api_mmsys.sbp>

#include "abmpglib.abp"

Declare Function waveOutOpen Lib "winmm" (phwo As *HWAVEOUT, uDeviceID As DWord, pwfx As *WAVEFORMATEX, dwCallback As DWord, dwInstance As DWord, fdwOpen As DWord) As MMRESULT
Declare Function waveOutClose Lib "winmm" (hwo As HWAVEOUT) As MMRESULT
Declare Function waveOutPrepareHeader Lib "winmm" (hwo As HWAVEOUT, pwh As *WAVEHDR, cbwh As DWord) As MMRESULT
Declare Function waveOutUnprepareHeader Lib "winmm" (hwo As HWAVEOUT, pwh As *WAVEHDR, cbwh As DWord) As MMRESULT
Declare Function waveOutWrite Lib "winmm" (hwo As HWAVEOUT, pwh As *WAVEHDR, cbwh As DWord) As MMRESULT
Declare Function waveOutPause Lib "winmm" (hwo As HWAVEOUT) As MMRESULT
Declare Function waveOutRestart Lib "winmm" (hwo As HWAVEOUT) As MMRESULT
Declare Function waveOutReset Lib "winmm" (hwo As HWAVEOUT) As MMRESULT
Declare Function waveOutGetPosition Lib "winmm" (hwo As HWAVEOUT, pmmt As *MMTIME, cbmmt As DWord) As MMRESULT
Declare Function MulDiv Lib "kernel32" (nNumber As Long, nNumerator As Long, nDenominator As Long) As Long

Const WAVE_MAPPER = (-1)
Const CALLBACK_FUNCTION = &H00030000l
Const WOM_OPEN = &H3BB
Const WOM_CLOSE = &H3BC
Const WOM_DONE = &H3BD
Const WHDR_DONE = &H00000001
Const WHDR_PREPARED = &H00000002
Const WHDR_BEGINLOOP = &H00000004
Const WHDR_ENDLOOP = &H00000008
Const WHDR_INQUEUE = &H00000010

Const TIME_MS = &H0001
Const TIME_SAMPLES = &H0002
Const TIME_BYTES = &H0004
Const TIME_SMPTE = &H0008
Const TIME_MIDI = &H0010
Const TIME_TICKS = &H0020

TypeDef MMRESULT = DWord
Typedef HWAVEOUT = VoidPtr
Type WAVEHDR
lpData As *Byte
dwBufferLength As DWord
dwBytesRecorded As DWord
dwUser As DWord
dwFlags As DWord
dwLoops As DWord
lpNext As *WAVEHDR
reserved As *DWord
End Type
Type MMTIME
wType As DWord
u As DWord
u2 As DWord
End Type

Class Mp3Player
Private
buffer[2] As *Byte
hwo As HWAVEOUT
wfe As WAVEFORMATEX

switch As Long

Function GetMp3Header() As Long
Dim head[3] As Byte
Dim r As DWord
Dim c As DWord
GetFormat(VarPtr(r), VarPtr(c))

wfe.wFormatTag = 1
wfe.nChannels = c
wfe.nSamplesPerSec = r
wfe.wBitsPerSample = 16
wfe.cbSize = Sizeof(WAVEFORMATEX)
wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample/8
wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign


GetMp3Header = 1
End Function

Sub waveOutProc(hwo As HWAVEOUT, uMsg As DWord, dwInstance As *DWord, dwParam1 As DWord, dwParam2 As DWord)
Dim x As *Mp3Player
x = dwInstance
Select Case uMsg
Case WOM_CLOSE
Case WOM_DONE
x->wh[1].dwUser = x->wh[1].dwUser - 1
If x->wh[0].dwUser = 0 Then x->read(hwo)
Case WOM_OPEN
End Select
End Sub

Public
wh[2] As WAVEHDR
Sub read(hwo As HWAVEOUT)
Dim r As DWord

If hwo = NULL Or wh[0].dwUser = 1 Or wh[1].dwUser > 1 Then Exit Sub
waveOutUnprepareHeader(hwo, VarPtr(wh[switch]), SizeOf(WAVEHDR))
r = ReadMp3(buffer[switch], wfe.nAvgBytesPerSec)
wh[switch].lpData = buffer[switch]
wh[switch].dwBufferLength = r
If r = 0 Then wh[0].dwUser = 1'これ以上再生しません
waveOutPrepareHeader(hwo, VarPtr(wh[switch]), SizeOf(WAVEHDR))
waveOutWrite(hwo, VarPtr(wh[switch]), SizeOf(WAVEHDR))
wh[1].dwUser = wh[1].dwUser + 1
switch = switch + 1
If switch = 2 Then switch = 0
End Sub

Function play(infile As *Byte) As Long
If hwo <> NULL Then Exit Function
If OpenMp3(infile) = FALSE Then Exit Function
If GetMp3Header() = 0 Then
CloseMp3()
Exit Function
End If
buffer[0] = malloc(wfe.nAvgBytesPerSec)
buffer[1] = malloc(wfe.nAvgBytesPerSec)
waveOutOpen( VarPtr(hwo), WAVE_MAPPER, VarPtr(wfe), AddressOf(waveOutProc) ,VarPtr(this), CALLBACK_FUNCTION)
read(hwo)
Sleep(500)
read(hwo)
play = 1
End Function

Sub stop() As Long
If hwo = NULL Then Exit Sub
wh[0].dwUser = 1
waveOutReset(hwo)
While wh[1].dwUser > 0
Sleep(1)
Wend
waveOutUnprepareHeader(hwo, VarPtr(wh[0]), SizeOf(WAVEHDR))
waveOutUnprepareHeader(hwo, VarPtr(wh[0]), SizeOf(WAVEHDR))
free(buffer[0])
free(buffer[1])

waveOutClose(hwo)
CloseMp3()
hwo = NULL
End Sub

Function state(t As *DWord) As Long
state = wh[1].dwUser
If t = NULL Then Exit Function
Dim mmt As MMTIME
mmt.wType = TIME_SAMPLES
waveOutGetPosition(hwo, VarPtr(mmt), SizeOf(MMTIME))
SetDWord(t,MulDiv(mmt.u , 1000, wfe.nSamplesPerSec))
End Function
End Class

#define SELFTEST
#ifdef SELFTEST
#N88BASIC
Dim wp As Mp3Player
Dim time As DWord
Print "再生"
wp.play("test.mp2")
While wp.state(VarPtr(time))
Locate 4,1
Print time\1000;"sec"
Sleep(1000)
Wend
wp.stop()
Print "停止"
#endif

ファイル名は好きに変えるようにして、
美しい音楽が聴こえてくるはずです。