Hallo AppGameKit users,
I am proud to present you a native ADPCM decoder in AppGameKit 2 Tier1 Basic.
I converted the code in the best way I could. I don't understand, how Pointers work in AGK. So I had to use some variables global.
The C-Code is from here.
http://faculty.salina.k-state.edu/tim/software/vox/vox.html#the-standard
This could be the beginning of some native format decoders. Maybe also encoders. ADPCM is a lossy AudioCompression with 1:4 compression ratio.
I don't realy know, I there is a need for, because AppGameKit can load OGG-Files and with 50% quality of an OGG, the OGG is smaller and cloud sound nearly the same.
But insteat of uncompressed WAVE RIFF PCM you could save space. And maybe I will later implement also an Encoder and Header-Writer, so we could have native IMA ADPCM in a WAVE RIFF Container or so.
This code is not bullet proof. If you use a great file, it will crash, there is no memory test. I used AudaCity to create the VOX-file and to test it back.
http://www.audacityteam.org/
What is ADPCM
https://de.wikipedia.org/wiki/Adaptive_Differential_Pulse_Code_Modulation
The idea I had in mind was, to be able to decode maybe more complex music formats like ADX
https://en.wikipedia.org/wiki/ADX_(file_format)
ADPCM is also used in game music. The Nintendo 64 supports native ADPCM and I think, the SNES is also capable to decode ADPCM.
For music there are some other formats, I am interested in and working on. (Tracker *hust* *hem* *hem*
)
The master you could find here
http://samples.mplayerhq.hu/A-codecs/CovoxFormats/
It's "woman_16-bit_44KHz_master.wav " about 485 KBytes and the VOX is only 121 KBytes.
The ZIP
contains the project (without the *.EXE) and the VOX-file for testing.
// Project: FLDEVOX by Xaby
// Created: 2017-04-06
/* converted from C to AGK2 by Xaby aka Folker Linstedt
// Source: http://faculty.salina.k-state.edu/tim/software/vox/vox.html#the-standard
// by Tim Bower
/* File: adpcm.c
Description: Routines to convert 12 bit linear samples to the
Dialogic or Oki ADPCM coding format.
I copied the algorithms out of the book "PC Telephony - The
complete guide to designing, building and programming systems
using Dialogic and Related Hardware" by Bob Edgar. pg 272-276.
Note: Edgar's book says that the second to last value is 1408; however,
* The standard says it is 1411.
* Changed on 1/17/2003.
*/
// show all errors
SetErrorMode(2)
// set window properties
SetWindowTitle( "FLDEVOX" )
SetWindowSize( 320, 200, 0 )
// set display properties
SetVirtualResolution( 320, 200 )
SetOrientationAllowed( 1, 1, 1, 1 )
SetSyncRate( 30, 0 ) // 30fps instead of 60 to save battery
UseNewDefaultFonts( 1 ) // since version 2.0.22 we can use nicer default fonts
Type adpcm_status
last as integer // short -127..128
step_index as integer // short -127..128
EndType
Global coder_stat as adpcm_status // global, because no classes?
Function adpcm_init( stat as adpcm_status)
stat.last = 0
stat.step_index = 0
coder_stat.last = 0 // only here because of global variable
coder_stat.step_index = 0 // only here because of global variable
EndFunction
Global adpcm_step_size as integer[49] = [16,17,19,21,23,25,28,31,34,37,41,45,50,55,60,66,73,80,88,97,107,118,130,143,157,173,190,209,230,253,279,307,337,371,408,449,494,544,598,658,724,796,876,963,1060,1166,1282,1411,1552]
Function step_adjust(code as integer)
value = 0
select( code && 0x07 )
case 0x00:
value=-1
endcase
case 0x01:
value=-1
endcase
case 0x02:
value=-1
endcase
case 0x03:
value=-1
endcase
case 0x04:
value=2
endcase
case 0x05:
value=4
endcase
case 0x06:
value=6
endcase
case 0x07:
value=8
endcase
endselect
Endfunction value
/*
* Decode Linear to ADPCM
*/
function adpcm_decode( code as integer) // without global: function adpcm_decode( code as integer, stat as adpcm_status)
diff as integer
E as integer
SS as integer
samp as integer
/* printf( "%x\t", code );
*/
SS = adpcm_step_size[coder_stat.step_index] // stat.step_index without global
E = SS/8
if ( code && 0x01 )
E = E+SS/4
endif
if ( code && 0x02 )
E =E+ SS/2
endif
if ( code && 0x04 )
E = E+SS
endif
// diff = (code & 0x08) ? -E : E;
if (code && 0x08) > 0 // maybe =1 ?
diff = -E
else
diff = E
endif
samp = coder_stat.last + diff // stat.last instead of coder_stat without global
/*
* Clip the values to +(2^11)-1 to -2^11. (12 bits 2's
* compelement)
* Note: previous version errantly clipped at +2048, which could
* cause a 2's complement overflow and was likely the source of
* clipping problems in the previous version. Thanks to Frank
* van Dijk for the correction. TLB 3/30/04
*/
if( samp > 2047 )
samp = 2047
endif
if( samp < -2048 )
samp = -2048
endif
coder_stat.last = samp // changed stat to coder_stat, because of *Pointer reference don't know how
coder_stat.step_index = coder_stat.step_index + step_adjust( code )
if( coder_stat.step_index < 0 ) : coder_stat.step_index = 0 : endif
if( coder_stat.step_index > 48 ) : coder_stat.step_index = 48 : endif
// how does stat will be returned?
// because of that, or the variable would not be set directly, I decided to make coder_stat global
Endfunction samp
Function LoadSoundVOX(filename$,frequenz)
Channels = 1
Bits = 16
// Mono, 16 Bit
adpcm_init( coder_stat ) // it's global, don't know, how to make a reference pointer *coder_stat
SoundID = -1
adpcmem = CreateMemblockFromFile(filename$) // vox without header RAW 4 Bit
// 1 Byte contains 2 Samples. "1 Byte contains 4 Bytes"
mox = CreateMemblock(GetMemblockSize(adpcmem)*4+12) // +12 for Header
SetMemblockByte(mox,0,Channels) // mono
SetMemblockByte(mox,2,Bits) // 16 Bit
SetMemblockInt (mox,4,frequenz) // 44100
SetMemblockInt (mox,8,GetMemblockSize(adpcmem)*2) // 4
// buffer12 is mox
for i=0 to GetMemblockSize(adpcmem)-1
SetMemblockShort(mox,12+i*4+0,adpcm_decode( (GetMemblockByte(adpcmem,i)>>4)&&0x0f )*16)
SetMemblockShort(mox,12+i*4+2,adpcm_decode( GetMemblockByte(adpcmem,i)&&0x0f )*16)
Next
//--- create SoundHeader ---
soundID = CreateSoundFromMemblock(mox)
// was for test only, is not a real WAVE-RIFF File, but works in AudaCity as RAW
//CreateFileFromMemblock("testfile_44100_16Bit_Mono.raw",mox)
DeleteMemblock(mox)
DeleteMemblock(adpcmem)
EndFunction soundID
voxid = LoadSoundVOX("woman_16-bit_44KHz_master.vox",44100) // Mono Only supported, but could be changed easily
If voxid <> -1
PlaySound(voxid)
EndIf
do
Print( "DeVOX, plays VOX-Files, ADPCM 4 Bit converts to 16 Bit, FPS: "+Str(ScreenFPS(),2) )
Print(voxid)
Sync()
loop
Please comment, what you are thinking of native support for other audio formats, or maybe other document types. And maybe you could tell me, how the global varible could be referenced like a pointer.
In Pascal the could do so, if you wrote the type of the variable in the function before the name of the variable.