MakeOGG
The program uses the Ogg Vorbis libraries from the Xiph.Org Foundation.
These Ogg libraries were compiled from:
libogg-1.3.6 and libvorbis-1.3.7 C source code, downloaded from:
https://downloads.xiph.org/releases/ogg/...3.6.tar.xz
https://downloads.xiph.org/releases/vorb...3.7.tar.xz
New Feature: Saving to OGG
Here is a new update: I have added support for saving to OGG format. This was a challenging project, but the results are interesting.
Important Notes:
32-bit Version: This includes 5 DLL files. One is compiled but unused (related to playback, seeking and metadata), so you don't need it because QB64PE handles that natively.
The qboggvorbis.dll file acts as a wrapper. This is necessary because the 32-bit DLL calling conventions differ from those used by QB64PE.
This wrapper resolves that compatibility issue (similar to the live visualization tool discussed earlier).
Note: vorbisfile.dll is included but not used here (it is strictly for playback, seeking, and metadata).
64-bit Version: This includes 4 DLL files. No wrapper is needed here; it works out of the box. Again, vorbisfile.dll is not used.
WARNING: Do NOT rename any of the DLL files. The internal dependencies are hardcoded in the source code. Renaming even a single DLL will break the linkage and cause "dynamic library not found" errors.
Folder Structure: I attempted to organize the 32-bit and 64-bit versions into separate subfolders within the program, but I couldn't get the path referencing to work properly. If anyone knows how to achieve this, I’d appreciate the tip. For now, the 32-bit and 64-bit versions are in separate folders. Since the filenames cannot be changed, please be careful not to mix them up.
Future Plans & Feedback: Finally, I have a question: What else would you be interested in seeing? I'm open to suggestions.
Thanks to this thread, I plan to rework my SaveSound library to consolidate everything nicely. There are a few more formats QB64PE can write directly (like AIFF, AIFC). Some others might require a full tracker implementation to be useful, or I need to research them further.
Zip file contains:
32bit version: qboggvorbis.dll warapper, wrapper source code in C, wrapper DEF file, mod music file, BAS file, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll.
64bit version: the same BAS source code, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll, s3m music file.
The program uses the Ogg Vorbis libraries from the Xiph.Org Foundation.
These Ogg libraries were compiled from:
libogg-1.3.6 and libvorbis-1.3.7 C source code, downloaded from:
https://downloads.xiph.org/releases/ogg/...3.6.tar.xz
https://downloads.xiph.org/releases/vorb...3.7.tar.xz
New Feature: Saving to OGG
Here is a new update: I have added support for saving to OGG format. This was a challenging project, but the results are interesting.
Important Notes:
32-bit Version: This includes 5 DLL files. One is compiled but unused (related to playback, seeking and metadata), so you don't need it because QB64PE handles that natively.
The qboggvorbis.dll file acts as a wrapper. This is necessary because the 32-bit DLL calling conventions differ from those used by QB64PE.
This wrapper resolves that compatibility issue (similar to the live visualization tool discussed earlier).
Note: vorbisfile.dll is included but not used here (it is strictly for playback, seeking, and metadata).
64-bit Version: This includes 4 DLL files. No wrapper is needed here; it works out of the box. Again, vorbisfile.dll is not used.
WARNING: Do NOT rename any of the DLL files. The internal dependencies are hardcoded in the source code. Renaming even a single DLL will break the linkage and cause "dynamic library not found" errors.
Folder Structure: I attempted to organize the 32-bit and 64-bit versions into separate subfolders within the program, but I couldn't get the path referencing to work properly. If anyone knows how to achieve this, I’d appreciate the tip. For now, the 32-bit and 64-bit versions are in separate folders. Since the filenames cannot be changed, please be careful not to mix them up.
Future Plans & Feedback: Finally, I have a question: What else would you be interested in seeing? I'm open to suggestions.
Thanks to this thread, I plan to rework my SaveSound library to consolidate everything nicely. There are a few more formats QB64PE can write directly (like AIFF, AIFC). Some others might require a full tracker implementation to be useful, or I need to research them further.
Zip file contains:
32bit version: qboggvorbis.dll warapper, wrapper source code in C, wrapper DEF file, mod music file, BAS file, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll.
64bit version: the same BAS source code, ogg.dll, vorbis.dll, vorbisenc.dll, vorbisfile.dll, s3m music file.
Code: (Select All)
Option _Explicit
' ============================================================
' QB64PE OGG/Vorbis encoder (DLL): _MemSound() -> .OGG
'
' 64-bit: calls original DLLs directly:
' ogg.dll, vorbis.dll, vorbisenc.dll (DO NOT RENAME)
'
' 32-bit: use stdcall bridge wrapper:
' qboggvorbis.dll (exports same symbol names)
' + original ogg.dll, vorbis.dll, vorbisenc.dll next to EXE
' ============================================================
' ---------------- USER SETTINGS ----------------
Const INPUT_FILE$ = "07.mp3" ' Any format _SndOpen can load
Const OUTPUT_OGG$ = "out.ogg"
Const VORBIS_QUALITY! = 0.30! ' VBR quality ~0.3..0.6 typical
Const CHUNK_FRAMES& = 1024
Const MAX_CHANNELS& = 16
$If 32BIT Then
Const PTR_BYTES& = 4
Const OGG_PAGE_BYTES& = 16
$Else
Const PTR_BYTES& = 8
Const OGG_PAGE_BYTES& = 32
$End If
' ---------------- DLL DECLARES ----------------
$If 32BIT Then
' x86: stdcall bridge (wrapper)
Declare Dynamic Library "qboggvorbis"
Function ogg_stream_init& Alias "ogg_stream_init" (ByVal os As _Offset, ByVal serialno As Long)
Function ogg_stream_packetin& Alias "ogg_stream_packetin" (ByVal os As _Offset, ByVal op As _Offset)
Function ogg_stream_pageout& Alias "ogg_stream_pageout" (ByVal os As _Offset, ByVal og As _Offset)
Function ogg_stream_flush& Alias "ogg_stream_flush" (ByVal os As _Offset, ByVal og As _Offset)
Function ogg_stream_clear& Alias "ogg_stream_clear" (ByVal os As _Offset)
Sub vorbis_info_init Alias "vorbis_info_init" (ByVal vi As _Offset)
Sub vorbis_info_clear Alias "vorbis_info_clear" (ByVal vi As _Offset)
Sub vorbis_comment_init Alias "vorbis_comment_init" (ByVal vc As _Offset)
Sub vorbis_comment_add_tag Alias "vorbis_comment_add_tag" (ByVal vc As _Offset, ByVal tag As _Offset, ByVal contents As _Offset)
Sub vorbis_comment_clear Alias "vorbis_comment_clear" (ByVal vc As _Offset)
Function vorbis_analysis_init& Alias "vorbis_analysis_init" (ByVal vd As _Offset, ByVal vi As _Offset)
Function vorbis_block_init& Alias "vorbis_block_init" (ByVal vd As _Offset, ByVal vb As _Offset)
Function vorbis_analysis_headerout& Alias "vorbis_analysis_headerout" (ByVal vd As _Offset, ByVal vc As _Offset, ByVal op As _Offset, ByVal op_comm As _Offset, ByVal op_code As _Offset)
Function vorbis_analysis_buffer%& Alias "vorbis_analysis_buffer" (ByVal vd As _Offset, ByVal vals As Long) ' returns float** as pointer
Function vorbis_analysis_wrote& Alias "vorbis_analysis_wrote" (ByVal vd As _Offset, ByVal vals As Long)
Function vorbis_analysis_blockout& Alias "vorbis_analysis_blockout" (ByVal vd As _Offset, ByVal vb As _Offset)
Function vorbis_analysis& Alias "vorbis_analysis" (ByVal vb As _Offset, ByVal op As _Offset) ' op may be NULL
Function vorbis_bitrate_addblock& Alias "vorbis_bitrate_addblock" (ByVal vb As _Offset)
Function vorbis_bitrate_flushpacket& Alias "vorbis_bitrate_flushpacket" (ByVal vd As _Offset, ByVal op As _Offset)
Function vorbis_block_clear& Alias "vorbis_block_clear" (ByVal vb As _Offset)
Sub vorbis_dsp_clear Alias "vorbis_dsp_clear" (ByVal vd As _Offset)
Function vorbis_encode_init_vbr& Alias "vorbis_encode_init_vbr" (ByVal vi As _Offset, ByVal channels As Long, ByVal rate As Long, ByVal base_quality As Single)
End Declare
$Else
' x64: direct calls to original DLLs
Declare Dynamic Library "ogg"
Function ogg_stream_init& Alias "ogg_stream_init" (ByVal os As _Offset, ByVal serialno As Long)
Function ogg_stream_packetin& Alias "ogg_stream_packetin" (ByVal os As _Offset, ByVal op As _Offset)
Function ogg_stream_pageout& Alias "ogg_stream_pageout" (ByVal os As _Offset, ByVal og As _Offset)
Function ogg_stream_flush& Alias "ogg_stream_flush" (ByVal os As _Offset, ByVal og As _Offset)
Function ogg_stream_clear& Alias "ogg_stream_clear" (ByVal os As _Offset)
End Declare
Declare Dynamic Library "vorbis"
Sub vorbis_info_init Alias "vorbis_info_init" (ByVal vi As _Offset)
Sub vorbis_info_clear Alias "vorbis_info_clear" (ByVal vi As _Offset)
Sub vorbis_comment_init Alias "vorbis_comment_init" (ByVal vc As _Offset)
Sub vorbis_comment_add_tag Alias "vorbis_comment_add_tag" (ByVal vc As _Offset, ByVal tag As _Offset, ByVal contents As _Offset)
Sub vorbis_comment_clear Alias "vorbis_comment_clear" (ByVal vc As _Offset)
Function vorbis_analysis_init& Alias "vorbis_analysis_init" (ByVal vd As _Offset, ByVal vi As _Offset)
Function vorbis_block_init& Alias "vorbis_block_init" (ByVal vd As _Offset, ByVal vb As _Offset)
Function vorbis_analysis_headerout& Alias "vorbis_analysis_headerout" (ByVal vd As _Offset, ByVal vc As _Offset, ByVal op As _Offset, ByVal op_comm As _Offset, ByVal op_code As _Offset)
Function vorbis_analysis_buffer%& Alias "vorbis_analysis_buffer" (ByVal vd As _Offset, ByVal vals As Long)
Function vorbis_analysis_wrote& Alias "vorbis_analysis_wrote" (ByVal vd As _Offset, ByVal vals As Long)
Function vorbis_analysis_blockout& Alias "vorbis_analysis_blockout" (ByVal vd As _Offset, ByVal vb As _Offset)
Function vorbis_analysis& Alias "vorbis_analysis" (ByVal vb As _Offset, ByVal op As _Offset)
Function vorbis_bitrate_addblock& Alias "vorbis_bitrate_addblock" (ByVal vb As _Offset)
Function vorbis_bitrate_flushpacket& Alias "vorbis_bitrate_flushpacket" (ByVal vd As _Offset, ByVal op As _Offset)
Function vorbis_block_clear& Alias "vorbis_block_clear" (ByVal vb As _Offset)
Sub vorbis_dsp_clear Alias "vorbis_dsp_clear" (ByVal vd As _Offset)
End Declare
Declare Dynamic Library "vorbisenc"
Function vorbis_encode_init_vbr& Alias "vorbis_encode_init_vbr" (ByVal vi As _Offset, ByVal channels As Long, ByVal rate As Long, ByVal base_quality As Single)
End Declare
$End If
' ============================================================
' MAIN
' ============================================================
Dim snd As Long
snd = _SndOpen(INPUT_FILE$)
If snd = 0 Then
Print "Cannot open input: "; INPUT_FILE$
End
End If
Dim src As _MEM
src = _MemSound(snd, 0)
If src.SIZE = 0 Then
Print "_MemSound returned SIZE=0."
_SndClose snd
End
End If
Dim ch As Long: ch = MemChannels(src)
If ch < 1 Or ch > MAX_CHANNELS& Then
Print "Bad channel count: "; ch; " (TYPE="; src.TYPE; " ELEMENTSIZE="; src.ELEMENTSIZE; ")"
_MemFree src
_SndClose snd
End
End If
Dim sr As Long: sr = _SndRate
Print "Input : "; INPUT_FILE$
Print "Output : "; OUTPUT_OGG$
Print "Channels : "; ch
Print "SampleRate: "; sr
Print "Quality : "; VORBIS_QUALITY!
If SaveMemSoundAsOggVorbis(src, ch, sr, OUTPUT_OGG$, VORBIS_QUALITY!) Then
Print "OK."
Else
Print "FAILED."
End If
_MemFree src
_SndClose snd
End
' ============================================================
' Core encoder: uses opaque memory blocks for lib structs
' ============================================================
Function SaveMemSoundAsOggVorbis% (src As _MEM, ch As Long, sr As Long, outFile$, quality As Single)
SaveMemSoundAsOggVorbis = 0
' Oversized opaque blocks (must be >= real struct sizes)
Dim vi As _MEM: vi = _MemNew(256)
Dim vc As _MEM: vc = _MemNew(2048)
Dim vd As _MEM: vd = _MemNew(16384)
Dim vb As _MEM: vb = _MemNew(8192)
Dim os As _MEM: os = _MemNew(8192)
Dim op As _MEM: op = _MemNew(512)
Dim op_comm As _MEM: op_comm = _MemNew(512)
Dim op_code As _MEM: op_code = _MemNew(512)
Dim og As _MEM: og = _MemNew(OGG_PAGE_BYTES&)
' Zero-fill blocks (safe)
_MemFill vi, vi.OFFSET, vi.SIZE, 0 As _Unsigned _Byte
_MemFill vc, vc.OFFSET, vc.SIZE, 0 As _Unsigned _Byte
_MemFill vd, vd.OFFSET, vd.SIZE, 0 As _Unsigned _Byte
_MemFill vb, vb.OFFSET, vb.SIZE, 0 As _Unsigned _Byte
_MemFill os, os.OFFSET, os.SIZE, 0 As _Unsigned _Byte
_MemFill op, op.OFFSET, op.SIZE, 0 As _Unsigned _Byte
_MemFill op_comm, op_comm.OFFSET, op_comm.SIZE, 0 As _Unsigned _Byte
_MemFill op_code, op_code.OFFSET, op_code.SIZE, 0 As _Unsigned _Byte
_MemFill og, og.OFFSET, og.SIZE, 0 As _Unsigned _Byte
Dim fh As Integer
fh = FreeFile
Open outFile$ For Binary As #fh
Dim ok As Integer: ok = -1
Dim serial As Long
Randomize Timer
serial = Int(Rnd * 2147483647)
' --- Vorbis init ---
vorbis_info_init vi.OFFSET
If vorbis_encode_init_vbr(vi.OFFSET, ch, sr, quality) <> 0 Then
Print "vorbis_encode_init_vbr failed (this is where x86 calling-convention mismatch usually kills the process)."
ok = 0
GoTo Cleanup
End If
vorbis_comment_init vc.OFFSET
AddTag vc, "ENCODER", "QB64PE"
AddTag vc, "TITLE", INPUT_FILE$
If vorbis_analysis_init(vd.OFFSET, vi.OFFSET) <> 0 Then
Print "vorbis_analysis_init failed."
ok = 0
GoTo Cleanup
End If
If vorbis_block_init(vd.OFFSET, vb.OFFSET) <> 0 Then
Print "vorbis_block_init failed."
ok = 0
GoTo Cleanup
End If
' --- Ogg stream init ---
If ogg_stream_init(os.OFFSET, serial) <> 0 Then
Print "ogg_stream_init failed."
ok = 0
GoTo Cleanup
End If
' --- Header packets ---
If vorbis_analysis_headerout(vd.OFFSET, vc.OFFSET, op.OFFSET, op_comm.OFFSET, op_code.OFFSET) <> 0 Then
Print "vorbis_analysis_headerout failed."
ok = 0
GoTo Cleanup
End If
If ogg_stream_packetin(os.OFFSET, op.OFFSET) <> 0 Then ok = 0: GoTo Cleanup
If ogg_stream_packetin(os.OFFSET, op_comm.OFFSET) <> 0 Then ok = 0: GoTo Cleanup
If ogg_stream_packetin(os.OFFSET, op_code.OFFSET) <> 0 Then ok = 0: GoTo Cleanup
Do While ogg_stream_flush(os.OFFSET, og.OFFSET) <> 0
WriteOggPageFromMem fh, og
Loop
' --- Audio frames ---
Dim framesTotal As _Integer64
framesTotal = src.SIZE \ src.ELEMENTSIZE
Dim poss As _Integer64: poss = 0
Dim framesThis As Long
Dim bufPP As _Offset
Dim mp As _MEM
Dim c As Long, f As Long
Dim N As Long
Dim chanPtr As _Offset
ReDim chanPtr(0 To ch - 1) As _Offset
Dim mchan As _MEM
Dim baseOfs As _Offset
Dim s As Single
Do While poss < framesTotal
framesThis = CHUNK_FRAMES&
If poss + framesThis > framesTotal Then framesThis = framesTotal - poss
bufPP = vorbis_analysis_buffer(vd.OFFSET, framesThis)
If bufPP = 0 Then
Print "vorbis_analysis_buffer returned NULL."
ok = 0
GoTo Cleanup
End If
mp = _Mem(bufPP, ch * PTR_BYTES&)
For c = 0 To ch - 1
chanPtr(c) = _MemGet(mp, mp.OFFSET + c * PTR_BYTES&, _Offset)
Next
_MemFree mp
For c = 0 To ch - 1
mchan = _Mem(chanPtr(c), framesThis * 4)
For f = 0 To framesThis - 1
baseOfs = src.OFFSET + (poss + f) * src.ELEMENTSIZE
s = SampleToFloat!(src, baseOfs, c)
If s > 1! Then s = 1!
If s < -1! Then s = -1!
_MemPut mchan, mchan.OFFSET + f * 4, s
Next
_MemFree mchan
Next
If vorbis_analysis_wrote(vd.OFFSET, framesThis) <> 0 Then
Print "vorbis_analysis_wrote failed."
ok = 0
GoTo Cleanup
End If
Do While vorbis_analysis_blockout(vd.OFFSET, vb.OFFSET) = 1
N = vorbis_analysis(vb.OFFSET, 0)
N = vorbis_bitrate_addblock(vb.OFFSET)
Do While vorbis_bitrate_flushpacket(vd.OFFSET, op.OFFSET) = 1
N = ogg_stream_packetin(os.OFFSET, op.OFFSET)
Do While ogg_stream_pageout(os.OFFSET, og.OFFSET) <> 0
WriteOggPageFromMem fh, og
Loop
Loop
Loop
poss = poss + framesThis
Loop
' End-of-stream
N = vorbis_analysis_wrote(vd.OFFSET, 0)
Do While vorbis_analysis_blockout(vd.OFFSET, vb.OFFSET) = 1
N = vorbis_analysis(vb.OFFSET, 0)
N = vorbis_bitrate_addblock(vb.OFFSET)
Do While vorbis_bitrate_flushpacket(vd.OFFSET, op.OFFSET) = 1
N = ogg_stream_packetin(os.OFFSET, op.OFFSET)
Do While ogg_stream_flush(os.OFFSET, og.OFFSET) <> 0
WriteOggPageFromMem fh, og
Loop
Loop
Loop
Cleanup:
Close #fh
N = ogg_stream_clear(os.OFFSET)
N = vorbis_block_clear(vb.OFFSET)
Call vorbis_dsp_clear(vd.OFFSET)
Call vorbis_comment_clear(vc.OFFSET)
Call vorbis_info_clear(vi.OFFSET)
_MemFree og
_MemFree op_code
_MemFree op_comm
_MemFree op
_MemFree os
_MemFree vb
_MemFree vd
_MemFree vc
_MemFree vi
If ok Then SaveMemSoundAsOggVorbis = -1
End Function
' Add Vorbis comment tag (ASCIIZ)
Sub AddTag (vc As _MEM, tag$, value$)
Dim tZ$, vZ$
tZ$ = tag$ + Chr$(0)
vZ$ = value$ + Chr$(0)
vorbis_comment_add_tag vc.OFFSET, _Offset(tZ$), _Offset(vZ$)
End Sub
' Convert one interleaved sample to float [-1..+1]
Function SampleToFloat! (src As _MEM, baseOfs As _Offset, c As Long)
Select Case src.TYPE
Case 260
SampleToFloat! = _MemGet(src, baseOfs + c * 4, Single)
Case 132
Dim l As Long
l = _MemGet(src, baseOfs + c * 4, Long)
SampleToFloat! = l / 2147483648!
Case 130
Dim i16 As Integer
i16 = _MemGet(src, baseOfs + c * 2, Integer)
SampleToFloat! = i16 / 32768!
Case 1153
Dim b As _Unsigned _Byte
b = _MemGet(src, baseOfs + c, _Unsigned _Byte)
SampleToFloat! = (CSng(b) - 128!) / 128!
Case Else
SampleToFloat! = 0!
End Select
End Function
' Write ogg_page (read fields from opaque ogg_page memory)
Sub WriteOggPageFromMem (fh As Integer, og As _MEM)
Dim headerPtr As _Offset, bodyPtr As _Offset
Dim headerLen As Long, bodyLen As Long
$If 32BIT Then
headerPtr = _MemGet(og, og.OFFSET + 0, _Offset)
headerLen = _MemGet(og, og.OFFSET + 4, Long)
bodyPtr = _MemGet(og, og.OFFSET + 8, _Offset)
bodyLen = _MemGet(og, og.OFFSET + 12, Long)
$Else
headerPtr = _MemGet(og, og.OFFSET + 0, _Offset)
headerLen = _MemGet(og, og.OFFSET + 8, Long)
bodyPtr = _MemGet(og, og.OFFSET + 16, _Offset)
bodyLen = _MemGet(og, og.OFFSET + 24, Long)
$End If
If headerLen > 0 Then
Dim s$
s$ = Space$(headerLen)
Dim mh As _MEM
mh = _Mem(headerPtr, headerLen)
_MemGet mh, mh.OFFSET, s$
_MemFree mh
Put #fh, , s$
End If
If bodyLen > 0 Then
Dim b$
b$ = Space$(bodyLen)
Dim mb As _MEM
mb = _Mem(bodyPtr, bodyLen)
_MemGet mb, mb.OFFSET, b$
_MemFree mb
Put #fh, , b$
End If
End Sub
' Detect channels from _MEMSOUND format (interleaved)
Function BytesPerSample% (t As Long)
Select Case t
Case 260, 132: BytesPerSample% = 4
Case 130: BytesPerSample% = 2
Case 1153: BytesPerSample% = 1
Case Else: BytesPerSample% = 0
End Select
End Function
Function MemChannels% (m As _MEM)
Dim bps As Long: bps = BytesPerSample%(m.TYPE)
If bps = 0 Then MemChannels% = 0: Exit Function
MemChannels% = m.ELEMENTSIZE \ bps
End Function

