implement 80/A0/B0 events

This commit is contained in:
wbcbz7 2024-04-06 15:12:38 +07:00
parent e1c50c9de0
commit cafc4a011b
8 changed files with 83 additions and 307 deletions

View file

@ -1,107 +0,0 @@
#pragma once
#include <stdint.h>
#pragma pack(push, 1)
struct lxm_header_stream_desc_t {
uint32_t ptr; // offset to data stream
uint32_t size; // stream data size in bytes
};
enum {
LXM_HEADER_LOG_VOLUME = (1 << 0),
LXM_HEADER_PITCH_TABLE = (1 << 1),
LXM_HEADER_DESYNC_TEST = (1 << 31),
};
struct lxm_header_t {
char magic[4]; // "LXM\x1A"
union {
struct {
uint8_t minor;
uint8_t major;
};
uint16_t v;
} version;
uint16_t flags; // reserved
uint16_t frame_rate; // in Hz, 8.8fx
uint16_t num_samples;
uint8_t num_channels; // 0..32
uint8_t callstack_depth; // max nested backrefs
uint16_t amplification; // 8.8fx, for software mixer
uint32_t samples_ptr; // offset to sample descriptors
//lxm_header_stream_desc_t stream[num_channels + 1]; // first is control stream
/*
if (flags & LXM_HEADER_PITCH_TABLE) {
uint16_t pitchtab_size; // 0..127
uint16_t pitchtab[pitchtab_size];
}
*/
};
// sample descriptors, stored sequentially
enum {
LXM_SAMPLE_8BIT = (0 << 0),
LXM_SAMPLE_16BIT = (1 << 0),
LXM_SAMPLE_FORMAT_MASK = (7 << 0),
LXM_SAMPLE_ONESHOT = (0 << 3),
LXM_SAMPLE_LOOP_FORWARD = (1 << 3),
LXM_SAMPLE_LOOP_BIDIR = (2 << 3),
LXM_SAMPLE_LOOP_MASK = (3 << 3),
LXM_SAMPLE_LOOP_SHIFT = 3,
};
struct lxm_sample_t {
uint16_t flags;
uint16_t compression; // currently 0
uint32_t sample_rate; // in hz, 24.8fx
int32_t length; // in samples, sustain loops not supported
int32_t loop_start; // in samples
uint32_t ofs_data; // offset in file
// optional, contains 0 if ignored
uint32_t max_sample_rate; // maximum sample rate occured in the file, used for target device sample rate optimization (emu8k :)
uint32_t opt_data; // sample optimization data (priority, etc)
uint32_t reserved;
};
// LXM v0 stream data:
enum {
LXM_STREAM_END_FRAME = 0xFF, // end of frame, set channel 0
LXM_STREAM_END = 0xFE, // end of stream, stop here or loop to LXM_STREAM_LOOP stream point
LXM_STREAM_NOP = 0xFD,
LXM_STREAM_NEW_ORDER = 0xFC, // nop, marks new order
LXM_STREAM_SET_FRAME_RATE = 0xFB, // word rate (as in lxm_header_t::frame_rate)
LXM_STREAM_LOOP = 0xFA, // set loop point here
// delay commands
LXM_STREAM_DELAY_INT32 = 0xF9, // dword delay
LXM_STREAM_DELAY_INT16 = 0xF8, // word delay
LXM_STREAM_DELAY_INT12 = 0xD0, // D0..DF - 0..4095 frames delay (hibyte in low 4 bits of command)
LXM_STERAM_DELAY_SHORT = 0xC0, // C0..CF - 1..16 frames delay
// back reference
LXM_STREAM_BACKREF = 0xE0, // E0..EF - word backrefpos (12 bit), byte frames
// setter commands
LXM_STREAM_VOLUME = 0x00, // 00..7F - volume is 7bit log compressed, end the frame
LXM_STREAM_SET = 0x80, // 80..9F - set all params
LXM_STREAM_RETRIG = 0xA0, // A0..AF - retrig note (ofs = 0), set all params
LXM_STREAM_SET_EXT = 0xB0, // B0..BF - reserved
// channel flags
LXM_STREAM_SET_SAMPLE = (1 << 0), // byte
LXM_STREAM_SET_PITCH = (1 << 1), // byte/word pitch/note compressed (see below)
LXM_STREAM_SET_PAN = (1 << 2), // byte left 00 .. 80 .. FF right
LXM_STREAM_SET_OFS = (1 << 3), // byte (sample_pos >> 8)
LXM_STREAM_RETRIG_END = (1 << 3), // end the frame
LXM_STREAM_SET_END = (1 << 4), // end the frame
};
#pragma pack(pop)

View file

@ -1,152 +0,0 @@
#pragma once
#include <stdint.h>
#include "lxmfile.h"
// forward declarations
struct lxm_channel_context_t;
struct lxm_sample_context_t;
union lxm_frac_t {
struct {
uint32_t f;
int32_t i;
};
int64_t p;
};
struct lxm_stream_stack_t {
uint8_t* stream;
uint32_t samples_to_play;
uint32_t reload;
};
struct lxm_channel_context_t {
lxm_sample_context_t* sample; // current sample
uint32_t pitch; // current pitch
uint32_t volume; // current volume
uint32_t pan; // current panning
// mixer context
struct {
int32_t vol_l, vol_r; // 16.16fx
lxm_frac_t pos; // 32.32fx
lxm_frac_t delta; // 32.32fx
bool play_backwards;
int stopped;
} mixer;
// stream stack
lxm_stream_stack_t stack[16];
int stack_pos;
// stream data
struct {
uint32_t samples_to_play;
uint32_t delay;
uint32_t reload;
uint8_t* data;
uint8_t* ptr;
uint8_t* loop; // if active
} stream;
bool updated;
};
struct lxm_sample_context_t {
lxm_sample_t header;
// mixer.delta = (channel.pitch * sample.rate) / global.rate = channel.pitch * rate_factor;
uint32_t rate_factor; // 16.16fx
// data unions
union {
int8_t* data8;
int16_t* data16;
};
};
struct lxm_mixer_context_t {
// next row delay count/reload
int32_t spt_count; // 16.16fx
int32_t spt_reload; // 16.16fx
uint32_t out_channels; // 1 or 2
uint32_t sample_rate; // sample rate
// amplify
uint32_t chan_amplify; // 16.16fx
};
struct lxm_context_t {
lxm_header_t header;
// sample contexts
lxm_sample_context_t* samples;
// channel context
lxm_channel_context_t channels[32];
// mixer context
lxm_mixer_context_t mixer;
// pitch table data
struct {
int size;
uint16_t* data;
} pitchtab;
// control stream data
struct {
uint32_t delay;
uint32_t reload;
uint8_t* data;
uint8_t* ptr;
} stream;
// position data
struct {
uint32_t order;
uint32_t frame;
uint64_t samples;
uint32_t looped;
} pos;
// loop data
struct {
uint8_t* data;
uint32_t frames;
uint64_t samples;
} loop;
};
// init context
int lxm_init(lxm_context_t* ctx, uint32_t out_channels, uint32_t sample_rate);
// free context
int lxm_free(lxm_context_t* ctx);
// load from file
int lxm_load(lxm_context_t* ctx, const char *filename);
// load from memory
int lxm_load_mem(lxm_context_t* ctx, const void *data, uint32_t size);
// reset to start
int lxm_rewind(lxm_context_t* ctx);
// play one tick
int lxm_tick(lxm_context_t* ctx);
// render int16_t stereo samples to buffer
// returns sample frames rendered or 0 if error
int lxm_render(lxm_context_t* ctx, int16_t* buf, int32_t count);

View file

@ -153,8 +153,6 @@
<ClInclude Include="include\opl3.h" />
<ClInclude Include="include\portaudio.h" />
<ClInclude Include="include\vgm.h" />
<ClInclude Include="lxmfile.h" />
<ClInclude Include="lxmplay.h" />
<ClInclude Include="opmfile.h" />
<ClInclude Include="opmplay.h" />
<ClInclude Include="wavehead.h" />

View file

@ -26,12 +26,6 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="lxmfile.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="lxmplay.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>
<ClInclude Include="wavehead.h">
<Filter>Файлы заголовков</Filter>
</ClInclude>

View file

@ -4,7 +4,7 @@
#pragma pack(push, 1)
struct opm_header_stream_desc_t {
uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
//uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
uint16_t size; // stream data size in bytes
};
@ -52,6 +52,9 @@ enum {
OPM_SET_ADSR = 0xA0, // A0..AF - set attack/sustain/decay/release for operator
OPM_SET_FREQ_FB = 0xB0, // B0..BF - set frequency + feedback
// control register set
OPM_CTRL_KEY_PERC = 0x00, // 00..7F - set key on/off for percussion, end of frame
OPM_CTRL_REG_SET = 0x80, // 80..BF - set control registers
// flags
OPM_KEY_OFF = (0 << 0),
@ -71,10 +74,16 @@ enum {
OPM_CMDA0_SELECT_OPERATOR = (1 << 2),
OPM_CMDA0_END_OF_FRAME = (1 << 3),
OPM_CMDB0_SET_FREQ_LOW = (1 << 0),
OPM_CMDB0_SET_FREQ_HIGH = (1 << 1),
OPM_CMDB0_SET_FEEDBACK = (1 << 2),
OPM_CMDB0_SET_FEEDBACK = (1 << 0),
OPM_CMDB0_SET_FREQ_LOW = (1 << 1),
OPM_CMDB0_SET_FREQ_HIGH = (1 << 2),
OPM_CMDB0_END_OF_FRAME = (1 << 3),
OPM_CTRL_SET_REG01 = (1 << 0),
OPM_CTRL_SET_REG08 = (1 << 1),
OPM_CTRL_SET_REG105 = (1 << 2),
OPM_CTRL_SET_REG104 = (1 << 3),
OPM_CTRL_SET_REGBD = (1 << 4),
};
#pragma pack(pop)

View file

@ -98,7 +98,6 @@ int opmplay_load_module(opmplay_context_t* ctx, opmplay_io_t* io) {
for (int ch = 0; ch < OPMPLAY_MAX_CHANNLES; ch++) {
ctx->channels[ch].stream.data = (uint8_t*)opmplay_alloc(sizeof(uint8_t*) * (streamdesc[ch].size));
if (ctx->channels[ch].stream.data == NULL) return OPMPLAY_ERR_MEMALLOC;
if (io->seek(io, streamdesc[ch].ptr << 4)) return OPMPLAY_ERR_IO;
if (io->read(io, ctx->channels[ch].stream.data, streamdesc[ch].size) != streamdesc[ch].size) return OPMPLAY_ERR_IO;
ctx->channels[ch].stream.delay = 1;
}
@ -180,6 +179,17 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
if ((*(data) & 0xE0) == OPM_CTRL_REG_SET) {
int mask = *data;
data++;
if (mask & OPM_CTRL_SET_REG01) OPL3_WriteRegBuffered(opl3, 0x001, *data++);
if (mask & OPM_CTRL_SET_REG08) OPL3_WriteRegBuffered(opl3, 0x008, *data++);
if (mask & OPM_CTRL_SET_REG105) OPL3_WriteRegBuffered(opl3, 0x105, *data++);
if (mask & OPM_CTRL_SET_REG104) OPL3_WriteRegBuffered(opl3, 0x104, *data++);
if (mask & OPM_CTRL_SET_REGBD) OPL3_WriteRegBuffered(opl3, 0x0BD, *data++);
continue;
}
// check for common stuff
switch (*data) {
// end of stream - rewind everything
@ -209,18 +219,15 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
break;
default:
#if 0
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
#endif
{
// register:data pair
OPL3_WriteRegBuffered(opl3, *(data + 0), *(data + 1));
data += 2;
printf("unknonw token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
}
@ -236,6 +243,33 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
bool isRun = true;
if (--chctx->stream.delay == 0) {
while (isRun) {
// get streams
if ((*(data) & 0xE0) == OPM_SET_MULT_WAVE_TL) {
int mask = *data;
int op = (mask & OPM_CMD80_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMD80_SET_MULT) OPL3_WriteRegBuffered(opl3, 0x20+op, *data++);
if (mask & OPM_CMD80_SET_TL) OPL3_WriteRegBuffered(opl3, 0x40+op, *data++);
if (mask & OPM_CMD80_SET_WAVEFORM) OPL3_WriteRegBuffered(opl3, 0xE0+op, *data++);
continue;
}
if ((*(data) & 0xF0) == OPM_SET_ADSR) {
int mask = *data;
int op = (mask & OPM_CMDA0_SELECT_OPERATOR ? 3 : 0) + opmplay_opregs_channel_offset[ch];
data++;
if (mask & OPM_CMDA0_SET_AD) OPL3_WriteRegBuffered(opl3, 0x60 + op, *data++);
if (mask & OPM_CMDA0_SET_SR) OPL3_WriteRegBuffered(opl3, 0x80 + op, *data++);
continue;
}
if ((*(data) & 0xF0) == OPM_SET_FREQ_FB) {
int mask = *data;
data++;
if (mask & OPM_CMDB0_SET_FEEDBACK) OPL3_WriteRegBuffered(opl3, 0xC0 + ch, *data++);
if (mask & OPM_CMDB0_SET_FREQ_LOW) OPL3_WriteRegBuffered(opl3, 0xA0 + ch, *data++);
if (mask & OPM_CMDB0_SET_FREQ_HIGH) OPL3_WriteRegBuffered(opl3, 0xB0 + ch, *data++);
continue;
}
// check for common stuff
switch (*data) {
case OPM_STREAM_END:
@ -258,41 +292,17 @@ int opmplay_tick(opmplay_context_t* ctx, opl3_chip* opl3) {
data++;
isRun = false;
break;
case OPM_STREAM_DELAY_INT16:
// delay (temporary)
chctx->stream.reload = *(data + 1) | (*(data + 2) << 8);
data += 3;
isRun = false;
break;
default:
#if 0
// test for delay
newdelay = opmplay_set_delay(&data);
if (newdelay > 0) {
chctx->stream.reload = newdelay;
}
else
#endif
{
switch (*(data + 0) & 0xF0) {
case 0x20:
case 0x40:
case 0x60:
case 0x80:
case 0xE0:
OPL3_WriteRegBuffered(opl3, *(data + 0) + opmplay_opregs_channel_offset[ch], *(data + 1));
break;
case 0xA0:
case 0xB0:
case 0xC0:
OPL3_WriteRegBuffered(opl3, *(data + 0) + ch, *(data + 1));
break;
default:
OPL3_WriteRegBuffered(opl3, *(data + 0), *(data + 1));
break;
}
data += 2;
printf("unknonw token %02x!\n", *data);
return OPMPLAY_ERR_BAD_FILE_STRUCTURE;
}
}
}

View file

@ -4,7 +4,7 @@
#pragma pack(push, 1)
struct opm_header_stream_desc_t {
uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
//uint16_t ptr; // offset to data stream in paragraphs (bytes*16)
uint16_t size; // stream data size in bytes
};
@ -74,10 +74,16 @@ enum {
OPM_CMDA0_SELECT_OPERATOR = (1 << 2),
OPM_CMDA0_END_OF_FRAME = (1 << 3),
OPM_CMDB0_SET_FREQ_LOW = (1 << 0),
OPM_CMDB0_SET_FREQ_HIGH = (1 << 1),
OPM_CMDB0_SET_FEEDBACK = (1 << 2),
OPM_CMDB0_SET_FEEDBACK = (1 << 0),
OPM_CMDB0_SET_FREQ_LOW = (1 << 1),
OPM_CMDB0_SET_FREQ_HIGH = (1 << 2),
OPM_CMDB0_END_OF_FRAME = (1 << 3),
OPM_CTRL_SET_REG01 = (1 << 0),
OPM_CTRL_SET_REG08 = (1 << 1),
OPM_CTRL_SET_REG105 = (1 << 2),
OPM_CTRL_SET_RE104 = (1 << 3),
OPM_CTRL_SET_REGBD = (1 << 4),
};
#pragma pack(pop)

View file

@ -337,6 +337,7 @@ int opm_write_file(opm_convert_context_t* ctx) {
std::vector<opm_write_file_info_t> writeinfo(9 + 1);
ctx->opmfile.streamdesc.resize(9 + 1);
#if 0
// calculate offsets
auto fsize = round_to_para(sizeof(ctx->opmfile.header) + (9 + 1) * sizeof(opm_header_stream_desc_t));
for (int i = 0; i < 9 + 1; i++) {
@ -363,6 +364,23 @@ int opm_write_file(opm_convert_context_t* ctx) {
fwrite(ctx->oplchan_out[i].data(), sizeof(uint8_t), ctx->oplchan_out[i].size(), f);
}
fclose(f);
#else
for (int i = 0; i < 9 + 1; i++) {
ctx->opmfile.streamdesc[i].size = ctx->oplchan_out[i].size();
}
// dump to OPM file
FILE* f = fopen(ctx->opmfile.filename.c_str(), "wb");
if (!f) return 1;
// write header
fwrite(&ctx->opmfile.header, sizeof(opm_header_t), 1, f);
fwrite(ctx->opmfile.streamdesc.data(), sizeof(decltype(ctx->opmfile.streamdesc)::value_type), ctx->opmfile.streamdesc.size(), f);
// write channel streams
for (int i = 0; i < 1 + 9; i++) {
fwrite(ctx->oplchan_out[i].data(), sizeof(uint8_t), ctx->oplchan_out[i].size(), f);
}
fclose(f);
#endif
}
#endif