Skip to content

Commit 67b0e56

Browse files
fix, bindings
1 parent e36206a commit 67b0e56

11 files changed

Lines changed: 259 additions & 42 deletions

File tree

CLAUDE.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,12 @@ TIC-80 includes FFT support for audio visualization and livecoding.
112112

113113
#### API Functions
114114
```lua
115-
value = fft(bin) -- Get raw FFT magnitude for bin (0-1023)
116-
value = ffts(bin) -- Get smoothed FFT magnitude for bin (0-1023)
115+
value = fft(bin) -- Get peak-normalized FFT magnitude for bin (0-1023)
116+
value = ffts(bin) -- Get smoothed peak-normalized FFT magnitude for bin (0-1023)
117117
```
118118

119+
**Note:** Both FFT functions return peak-normalized values (auto-gain controlled), not raw magnitudes.
120+
119121
### VQT Implementation
120122

121123
Constant-Q Transform provides logarithmic frequency spacing for better musical analysis.
@@ -147,11 +149,13 @@ Constant-Q Transform provides logarithmic frequency spacing for better musical a
147149

148150
#### API Functions
149151
```lua
150-
value = vqt(bin) -- Get raw VQT magnitude for bin (0-119)
152+
value = vqt(bin) -- Get peak-normalized VQT magnitude for bin (0-119)
151153
-- Note mapping: Bin = octave * 12 + note
152154
-- Note: C=0, C#=1, D=2, D#=3, E=4, F=5, F#=6, G=7, G#=8, A=9, A#=10, B=11
153155
```
154156

157+
**Note:** VQT also returns peak-normalized values (auto-gain controlled), not raw magnitudes.
158+
155159
### FFT vs VQT Comparison
156160

157161
| Aspect | FFT | VQT |
@@ -206,9 +210,10 @@ local color = (bassNote % 12) + 1 -- Color from note
206210
### Completed Features
207211
- **FFT**: 1024 bins with exact original behavior preserved
208212
- **VQT**: 120 bins with Variable-Q implementation
209-
- **Spectral Whitening**: Per-bin normalization for VQT
213+
- **Spectral Whitening**: Per-bin normalization for VQT (removed due to spreading issues)
210214
- **Shared Audio Buffer**: Automatic sizing for both FFT and VQT
211-
- **Lua API**: `fft()`, `ffts()`, `vqt()` functions implemented
215+
- **API Functions**: `fft()`, `ffts()`, `vqt()`, `vqts()` implemented for all supported languages
216+
- **Peak Normalization**: Both FFT and VQT use auto-gain control
212217

213218
### Configuration Options
214219
- `VQT_FFT_SIZE`: Default 8192 (configurable in vqtdata.h)
@@ -226,9 +231,13 @@ local color = (bassNote % 12) + 1 -- Color from note
226231
## Future Enhancements
227232

228233
### Additional API Functions
229-
- `vqts(bin)`: Smoothed VQT data
230-
- `vqto(octave, note)`: Raw VQT by musical note
234+
- `vqts(bin)`: Smoothed VQT data (already implemented)
235+
- `vqto(octave, note)`: VQT by musical note
231236
- `vqtos(octave, note)`: Smoothed VQT by musical note
237+
- `fftr(bin)`: Raw (non-normalized) FFT magnitude
238+
- `fftrs(bin)`: Raw smoothed FFT magnitude
239+
- `vqtr(bin)`: Raw (non-normalized) VQT magnitude
240+
- `vqtrs(bin)`: Raw smoothed VQT magnitude
232241

233242
### Signal Processing Enhancements
234243
- **Adaptive Thresholding**: Dynamic noise floor removal

src/api.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,37 @@ enum
822822
1, \
823823
0, \
824824
double, \
825-
tic_mem*, s32 startFreq, s32 endFreq)
825+
tic_mem*, s32 startFreq, s32 endFreq) \
826+
\
827+
\
828+
macro(vqt, \
829+
"vqt(bin)", \
830+
\
831+
"Get Variable-Q Transform magnitude for a specific frequency bin.\n" \
832+
"VQT provides 120 bins (0-119) with logarithmic frequency spacing for musical analysis.\n" \
833+
"Each bin corresponds to a musical note: bin = octave * 12 + note\n" \
834+
"where octave is 0-9 and note is 0-11 (C=0, C#=1, D=2, ..., B=11).\n" \
835+
"Returns a value roughly 0..1 based on the intensity at that frequency.", \
836+
1, \
837+
1, \
838+
0, \
839+
double, \
840+
tic_mem*, s32 bin) \
841+
\
842+
\
843+
macro(vqts, \
844+
"vqts(bin)", \
845+
\
846+
"Get smoothed Variable-Q Transform magnitude for a specific frequency bin.\n" \
847+
"VQT provides 120 bins (0-119) with logarithmic frequency spacing for musical analysis.\n" \
848+
"Each bin corresponds to a musical note: bin = octave * 12 + note\n" \
849+
"where octave is 0-9 and note is 0-11 (C=0, C#=1, D=2, ..., B=11).\n" \
850+
"Returns a smoothed value roughly 0..1 based on the intensity at that frequency.", \
851+
1, \
852+
1, \
853+
0, \
854+
double, \
855+
tic_mem*, s32 bin)
826856

827857
#define TIC_API_DEF(name, _, __, ___, ____, _____, ret, ...) ret tic_api_##name(__VA_ARGS__);
828858
TIC_API_LIST(TIC_API_DEF)

src/api/janet.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ static Janet janet_fget(int32_t argc, Janet* argv);
8181
static Janet janet_fset(int32_t argc, Janet* argv);
8282
static Janet janet_fft(int32_t argc, Janet* argv);
8383
static Janet janet_ffts(int32_t argc, Janet* argv);
84+
static Janet janet_vqt(int32_t argc, Janet* argv);
85+
static Janet janet_vqts(int32_t argc, Janet* argv);
8486

8587
static void closeJanet(tic_mem* tic);
8688
static bool initJanet(tic_mem* tic, const char* code);
@@ -146,6 +148,8 @@ static const JanetReg janet_c_functions[] =
146148
{"fset", janet_fset, NULL},
147149
{"fft", janet_fft, NULL},
148150
{"ffts", janet_ffts, NULL},
151+
{"vqt", janet_vqt, NULL},
152+
{"vqts", janet_vqts, NULL},
149153
{NULL, NULL, NULL}
150154
};
151155

@@ -1081,7 +1085,27 @@ static Janet janet_ffts(int32_t argc, Janet* argv)
10811085
if (argc >= 2) end_freq = janet_getinteger(argv, 1);
10821086

10831087
tic_core* core = getJanetMachine(); tic_mem* tic = (tic_mem*)core;
1084-
return janet_wrap_number(core->api.fft(tic, start_freq, end_freq));
1088+
return janet_wrap_number(core->api.ffts(tic, start_freq, end_freq));
1089+
}
1090+
1091+
static Janet janet_vqt(int32_t argc, Janet* argv)
1092+
{
1093+
janet_fixarity(argc, 1);
1094+
1095+
s32 bin = janet_getinteger(argv, 0);
1096+
1097+
tic_core* core = getJanetMachine(); tic_mem* tic = (tic_mem*)core;
1098+
return janet_wrap_number(core->api.vqt(tic, bin));
1099+
}
1100+
1101+
static Janet janet_vqts(int32_t argc, Janet* argv)
1102+
{
1103+
janet_fixarity(argc, 1);
1104+
1105+
s32 bin = janet_getinteger(argv, 0);
1106+
1107+
tic_core* core = getJanetMachine(); tic_mem* tic = (tic_mem*)core;
1108+
return janet_wrap_number(core->api.vqts(tic, bin));
10851109
}
10861110

10871111
/* ***************** */

src/api/js.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,22 @@ static JSValue js_ffts(JSContext *ctx, JSValueConst this_val, s32 argc, JSValueC
10321032
return JS_NewFloat64(ctx, core->api.ffts(tic, start_freq, end_freq));
10331033
}
10341034

1035+
static JSValue js_vqt(JSContext *ctx, JSValueConst this_val, s32 argc, JSValueConst *argv)
1036+
{
1037+
tic_core* core = getCore(ctx); tic_mem* tic = (tic_mem*)core;
1038+
s32 bin = getInteger(ctx, argv[0]);
1039+
1040+
return JS_NewFloat64(ctx, core->api.vqt(tic, bin));
1041+
}
1042+
1043+
static JSValue js_vqts(JSContext *ctx, JSValueConst this_val, s32 argc, JSValueConst *argv)
1044+
{
1045+
tic_core* core = getCore(ctx); tic_mem* tic = (tic_mem*)core;
1046+
s32 bin = getInteger(ctx, argv[0]);
1047+
1048+
return JS_NewFloat64(ctx, core->api.vqts(tic, bin));
1049+
}
1050+
10351051
static bool initJavascript(tic_mem* tic, const char* code)
10361052
{
10371053
closeJavascript(tic);

src/api/luaapi.c

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// SOFTWARE.
2222

2323
#include "core/core.h"
24-
#include "vqtdata.h"
24+
#include "ext/vqt.h"
2525

2626
#include <stdlib.h>
2727
#include <lua.h>
@@ -1598,25 +1598,15 @@ static s32 lua_ffts(lua_State* lua)
15981598

15991599
static s32 lua_vqt(lua_State* lua)
16001600
{
1601+
tic_core* core = getLuaCore(lua);
1602+
tic_mem* tic = (tic_mem*)core;
16011603
s32 top = lua_gettop(lua);
16021604

16031605
if (top >= 1)
16041606
{
16051607
s32 bin = getLuaNumber(lua, 1);
16061608

1607-
// Validate bin range
1608-
if (bin < 0 || bin >= VQT_BINS)
1609-
{
1610-
luaL_error(lua, "vqt bin out of range (0-%d)\n", VQT_BINS - 1);
1611-
return 0;
1612-
}
1613-
1614-
#ifdef TIC80_FFT_UNSUPPORTED
1615-
lua_pushnumber(lua, 0.0);
1616-
#else
1617-
// Return raw VQT data (normalized but unsmoothed)
1618-
lua_pushnumber(lua, vqtData[bin] / vqtPeakSmoothValue);
1619-
#endif
1609+
lua_pushnumber(lua, tic_api_vqt(tic, bin));
16201610
return 1;
16211611
}
16221612

@@ -1626,26 +1616,15 @@ static s32 lua_vqt(lua_State* lua)
16261616

16271617
static s32 lua_vqts(lua_State* lua)
16281618
{
1619+
tic_core* core = getLuaCore(lua);
1620+
tic_mem* tic = (tic_mem*)core;
16291621
s32 top = lua_gettop(lua);
16301622

16311623
if (top >= 1)
16321624
{
16331625
s32 bin = getLuaNumber(lua, 1);
16341626

1635-
// Validate bin range
1636-
if (bin < 0 || bin >= VQT_BINS)
1637-
{
1638-
luaL_error(lua, "vqts bin out of range (0-%d)\n", VQT_BINS - 1);
1639-
return 0;
1640-
}
1641-
1642-
#ifdef TIC80_FFT_UNSUPPORTED
1643-
lua_pushnumber(lua, 0.0);
1644-
#else
1645-
// Return smoothed VQT data (smoothed + normalized)
1646-
// This gives more stable values
1647-
lua_pushnumber(lua, vqtNormalizedData[bin]);
1648-
#endif
1627+
lua_pushnumber(lua, tic_api_vqts(tic, bin));
16491628
return 1;
16501629
}
16511630

@@ -1707,10 +1686,6 @@ void luaapi_init(tic_core* core)
17071686

17081687
registerLuaFunction(core, lua_dofile, "dofile");
17091688
registerLuaFunction(core, lua_loadfile, "loadfile");
1710-
1711-
// Register VQT functions
1712-
registerLuaFunction(core, lua_vqt, "vqt");
1713-
registerLuaFunction(core, lua_vqts, "vqts");
17141689
}
17151690

17161691
void luaapi_close(tic_mem* tic)

src/api/mruby.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,44 @@ static mrb_value mrb_ffts(mrb_state* mrb, mrb_value self)
568568
}
569569
}
570570

571+
static mrb_value mrb_vqt(mrb_state* mrb, mrb_value self)
572+
{
573+
mrb_int bin;
574+
mrb_int argc = mrb_get_args(mrb, "i", &bin);
575+
576+
tic_core* core = getMRubyMachine(mrb);
577+
tic_mem* tic = (tic_mem*)core;
578+
579+
if (argc == 0)
580+
{
581+
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid params, vqt(bin)\n");
582+
return mrb_nil_value();
583+
}
584+
else
585+
{
586+
return mrb_float_value(mrb, core->api.vqt(tic, bin));
587+
}
588+
}
589+
590+
static mrb_value mrb_vqts(mrb_state* mrb, mrb_value self)
591+
{
592+
mrb_int bin;
593+
mrb_int argc = mrb_get_args(mrb, "i", &bin);
594+
595+
tic_core* core = getMRubyMachine(mrb);
596+
tic_mem* tic = (tic_mem*)core;
597+
598+
if (argc == 0)
599+
{
600+
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid params, vqts(bin)\n");
601+
return mrb_nil_value();
602+
}
603+
else
604+
{
605+
return mrb_float_value(mrb, core->api.vqts(tic, bin));
606+
}
607+
}
608+
571609
typedef struct
572610
{
573611
mrb_state* mrb;

src/api/scheme.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,28 @@ s7_pointer scheme_ffts(s7_scheme* sc, s7_pointer args)
772772
return s7_make_real(sc, core->api.ffts(tic, start_freq, end_freq));
773773
}
774774

775+
s7_pointer scheme_vqt(s7_scheme* sc, s7_pointer args)
776+
{
777+
// vqt(int bin) -> float_value
778+
tic_core* core = getSchemeCore(sc);
779+
tic_mem* tic = (tic_mem*)core;
780+
const int argn = s7_list_length(sc, args);
781+
const s32 bin = argn > 0 ? s7_integer(s7_car(args)) : 0;
782+
783+
return s7_make_real(sc, core->api.vqt(tic, bin));
784+
}
785+
786+
s7_pointer scheme_vqts(s7_scheme* sc, s7_pointer args)
787+
{
788+
// vqts(int bin) -> float_value
789+
tic_core* core = getSchemeCore(sc);
790+
tic_mem* tic = (tic_mem*)core;
791+
const int argn = s7_list_length(sc, args);
792+
const s32 bin = argn > 0 ? s7_integer(s7_car(args)) : 0;
793+
794+
return s7_make_real(sc, core->api.vqts(tic, bin));
795+
}
796+
775797
static void initAPI(tic_core* core)
776798
{
777799
s7_scheme* sc = core->currentVM;

src/api/squirrel.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,46 @@ static SQInteger squirrel_ffts(HSQUIRRELVM vm)
15811581
return 0;
15821582
}
15831583

1584+
static SQInteger squirrel_vqt(HSQUIRRELVM vm)
1585+
{
1586+
tic_core* core = getSquirrelCore(vm);
1587+
tic_mem* tic = (tic_mem*)core;
1588+
1589+
SQInteger top = sq_gettop(vm);
1590+
1591+
if (top >= 2)
1592+
{
1593+
double bin = getSquirrelNumber(vm, 2);
1594+
1595+
sq_pushfloat(vm, (SQFloat)(core->api.vqt(tic, bin)));
1596+
return 1;
1597+
}
1598+
1599+
sq_throwerror(vm, "invalid params, vqt(bin)\n");
1600+
1601+
return 0;
1602+
}
1603+
1604+
static SQInteger squirrel_vqts(HSQUIRRELVM vm)
1605+
{
1606+
tic_core* core = getSquirrelCore(vm);
1607+
tic_mem* tic = (tic_mem*)core;
1608+
1609+
SQInteger top = sq_gettop(vm);
1610+
1611+
if (top >= 2)
1612+
{
1613+
double bin = getSquirrelNumber(vm, 2);
1614+
1615+
sq_pushfloat(vm, (SQFloat)(core->api.vqts(tic, bin)));
1616+
return 1;
1617+
}
1618+
1619+
sq_throwerror(vm, "invalid params, vqts(bin)\n");
1620+
1621+
return 0;
1622+
}
1623+
15841624
static SQInteger squirrel_dofile(HSQUIRRELVM vm)
15851625
{
15861626
return sq_throwerror(vm, "unknown method: \"dofile\"\n");

0 commit comments

Comments
 (0)