Applied the recent mainline PCem commits (and fixed the Pentium machines);

Ported the Roland MT-32 emulation (using MUNT) from bit's MT32 emulation branch of PCem;
Sanitized the OpenAL give buffer code in openal.c a bit;
NVR path is now specifiable in the Settings dialog;
Added Logitech 3-button serial mouse per protocol description by waltje;
The RTL8029AS and the BT-958D now actually use the PCI IRQ routing;
Fixed BT-958D PCI device initialization on the bus;
PCI IRQ routing now respects the edge/level setting set on ports 4D0/4D1.
This commit is contained in:
OBattler
2017-06-19 06:46:08 +02:00
parent 9c9f7b1b9a
commit 1e7668f1db
112 changed files with 16289 additions and 732 deletions

View File

@@ -2,10 +2,10 @@
- U/V integer pairing
- FPU/FXCH pairing
- Prefix decode delay (including shadowing)
- FPU latencies
Elements not taken into account :
- Branch prediction (beyond most simplistic approximation)
- PMMX decode queue
- FPU latencies
- MMX latencies
*/
@@ -17,6 +17,8 @@
#include "../mem.h"
#include "codegen.h"
/*Instruction has different execution time for 16 and 32 bit data. Does not pair */
#define CYCLES_HAS_MULTI (1 << 28)
@@ -37,69 +39,126 @@ static int pair_timings[4][4] =
/*Instruction follows either register timing, read-modify, or read-modify-write.
May be pairable*/
#define CYCLES_REG (0 << 0)
#define CYCLES_RM (1 << 0)
#define CYCLES_RMW (2 << 0)
#define CYCLES_BRANCH (3 << 0)
#define CYCLES_REG (0ull << 0)
#define CYCLES_RM (1ull << 0)
#define CYCLES_RMW (2ull << 0)
#define CYCLES_BRANCH (3ull << 0)
#define CYCLES_MASK ((1 << 7) - 1)
/*Instruction has immediate data. Can only be used with PAIR_U/PAIR_V/PAIR_UV*/
#define CYCLES_HASIMM (3ull << 2)
#define CYCLES_IMM8 (1ull << 2)
#define CYCLES_IMM1632 (2ull << 2)
#define CYCLES_MASK ((1ull << 7) - 1)
/*Instruction is MMX shift or pack/unpack instruction*/
#define MMX_SHIFTPACK (1 << 7)
#define MMX_SHIFTPACK (1ull << 7)
/*Instruction is MMX multiply instruction*/
#define MMX_MULTIPLY (1 << 8)
#define MMX_MULTIPLY (1ull << 8)
/*Instruction does not pair*/
#define PAIR_NP (0 << 29)
#define PAIR_NP (0ull << 29)
/*Instruction pairs in U pipe only*/
#define PAIR_U (1 << 29)
#define PAIR_U (1ull << 29)
/*Instruction pairs in V pipe only*/
#define PAIR_V (2 << 29)
#define PAIR_V (2ull << 29)
/*Instruction pairs in both U and V pipes*/
#define PAIR_UV (3 << 29)
#define PAIR_UV (3ull << 29)
/*Instruction pairs in U pipe only and only with FXCH*/
#define PAIR_FX (5 << 29)
#define PAIR_FX (5ull << 29)
/*Instruction is FXCH and only pairs in V pipe with FX pairable instruction*/
#define PAIR_FXCH (6 << 29)
#define PAIR_FXCH (6ull << 29)
#define PAIR_MASK (7 << 29)
#define PAIR_FPU (4ull << 29)
#define PAIR_MASK (7ull << 29)
/*Instruction has input dependency on register in REG field*/
#define SRCDEP_REG (1 << 9)
#define SRCDEP_REG (1ull << 9)
/*Instruction has input dependency on register in R/M field*/
#define SRCDEP_RM (1 << 10)
#define SRCDEP_RM (1ull << 10)
/*Instruction modifies register in REG field*/
#define DSTDEP_REG (1 << 11)
#define DSTDEP_REG (1ull << 11)
/*Instruction modifies register in R/M field*/
#define DSTDEP_RM (1 << 12)
#define DSTDEP_RM (1ull << 12)
/*Instruction has input dependency on given register*/
#define SRCDEP_EAX (1 << 13)
#define SRCDEP_ECX (1 << 14)
#define SRCDEP_EDX (1 << 15)
#define SRCDEP_EBX (1 << 16)
#define SRCDEP_ESP (1 << 17)
#define SRCDEP_EBP (1 << 18)
#define SRCDEP_ESI (1 << 19)
#define SRCDEP_EDI (1 << 20)
#define SRCDEP_EAX (1ull << 13)
#define SRCDEP_ECX (1ull << 14)
#define SRCDEP_EDX (1ull << 15)
#define SRCDEP_EBX (1ull << 16)
#define SRCDEP_ESP (1ull << 17)
#define SRCDEP_EBP (1ull << 18)
#define SRCDEP_ESI (1ull << 19)
#define SRCDEP_EDI (1ull << 20)
/*Instruction modifies given register*/
#define DSTDEP_EAX (1 << 21)
#define DSTDEP_ECX (1 << 22)
#define DSTDEP_EDX (1 << 23)
#define DSTDEP_EBX (1 << 24)
#define DSTDEP_ESP (1 << 25)
#define DSTDEP_EBP (1 << 26)
#define DSTDEP_ESI (1 << 27)
#define DSTDEP_EDI (1 << 28)
#define DSTDEP_EAX (1ull << 21)
#define DSTDEP_ECX (1ull << 22)
#define DSTDEP_EDX (1ull << 23)
#define DSTDEP_EBX (1ull << 24)
#define DSTDEP_ESP (1ull << 25)
#define DSTDEP_EBP (1ull << 26)
#define DSTDEP_ESI (1ull << 27)
#define DSTDEP_EDI (1ull << 28)
/*Instruction pops the FPU stack*/
#define FPU_POP (1ull << 32)
/*Instruction pops the FPU stack twice*/
#define FPU_POP2 (1ull << 33)
/*Instruction pushes onto the FPU stack*/
#define FPU_PUSH (1ull << 34)
/*Instruction writes to ST(0)*/
#define FPU_WRITE_ST0 (1ull << 35)
/*Instruction reads from ST(0)*/
#define FPU_READ_ST0 (1ull << 36)
/*Instruction reads from and writes to ST(0)*/
#define FPU_RW_ST0 (3ull << 35)
/*Instruction reads from ST(1)*/
#define FPU_READ_ST1 (1ull << 37)
/*Instruction writes to ST(1)*/
#define FPU_WRITE_ST1 (1ull << 38)
/*Instruction reads from and writes to ST(1)*/
#define FPU_RW_ST1 (3ull << 37)
/*Instruction reads from ST(reg)*/
#define FPU_READ_STREG (1ull << 39)
/*Instruction writes to ST(reg)*/
#define FPU_WRITE_STREG (1ull << 40)
/*Instruction reads from and writes to ST(reg)*/
#define FPU_RW_STREG (3ull << 39)
/*comp_time = cycles until instruction complete
i_overlap = cycles that overlap with integer
f_overlap = cycles that overlap with subsequent FPU*/
#define FPU_CYCLES(comp_time, i_overlap, f_overlap) ((uint64_t)comp_time) | ((uint64_t)i_overlap << 41) | ((uint64_t)f_overlap << 49) | PAIR_FPU
#define FPU_COMP_TIME(timing) (timing & 0xff)
#define FPU_I_OVERLAP(timing) ((timing >> 41) & 0xff)
#define FPU_F_OVERLAP(timing) ((timing >> 49) & 0xff)
#define FPU_I_LATENCY(timing) (FPU_COMP_TIME(timing) - FPU_I_OVERLAP(timing))
#define FPU_F_LATENCY(timing) (FPU_I_OVERLAP(timing) - FPU_F_OVERLAP(timing))
#define FPU_RESULT_LATENCY(timing) ((timing >> 41) & 0xff)
#define FPU_FXCH (1ull << 56)
#define INVALID 0
static int u_pipe_full;
static uint32_t u_pipe_opcode;
static uint32_t *u_pipe_timings;
static uint64_t *u_pipe_timings;
static uint32_t u_pipe_op_32;
static uint32_t u_pipe_regmask;
static uint32_t u_pipe_fetchdat;
static int u_pipe_decode_delay_offset;
static int fpu_latency;
static int fpu_st_latency[8];
#define REGMASK_SHIFTPACK (1 << 8)
#define REGMASK_MULTIPLY (1 << 8)
@@ -155,7 +214,7 @@ static uint32_t get_dstdep_mask(uint32_t data, uint32_t fetchdat, int bit8)
return mask;
}
static uint32_t opcode_timings[256] =
static uint64_t opcode_timings[256] =
{
/* ADD ADD ADD ADD*/
/*00*/ PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RM | SRCDEP_REG | DSTDEP_REG, PAIR_UV | CYCLES_RM | SRCDEP_REG | DSTDEP_REG,
@@ -291,43 +350,43 @@ static uint32_t opcode_timings[256] =
PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(2), PAIR_UV | CYCLES_RMW, INVALID
};
static uint32_t opcode_timings_mod3[256] =
static uint64_t opcode_timings_mod3[256] =
{
/* ADD ADD ADD ADD*/
/*00*/ PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* ADD ADD PUSH ES POP ES*/
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
/* OR OR OR OR*/
PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* OR OR PUSH CS */
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_NP | CYCLES(1), INVALID,
PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_NP | CYCLES(1), INVALID,
/* ADC ADC ADC ADC*/
/*10*/ PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* ADC ADC PUSH SS POP SS*/
PAIR_U | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_U | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
PAIR_U | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_U | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
/* SBB SBB SBB SBB*/
PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_U | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* SBB SBB PUSH DS POP DS*/
PAIR_U | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_U | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
PAIR_U | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_U | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(3),
/* AND AND AND AND*/
/*20*/ PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* AND AND DAA*/
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
/* SUB SUB SUB SUB*/
PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* SUB SUB DAS*/
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
/* XOR XOR XOR XOR*/
/*30*/ PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM | DSTDEP_REG,
/* XOR XOR AAA*/
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
/* CMP CMP CMP CMP*/
PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM, PAIR_UV | CYCLES_REG | SRCDEP_REG | SRCDEP_RM, PAIR_UV | CYCLES_REG | SRCDEP_RM | SRCDEP_REG,
/* CMP CMP AAS*/
PAIR_UV | CYCLES_REG | SRCDEP_EAX | SRCDEP_RM, PAIR_UV | CYCLES_REG | SRCDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
PAIR_UV | CYCLES_REG | SRCDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_EAX, INVALID, PAIR_NP | CYCLES(3),
/* INC EAX INC ECX INC EDX INC EBX*/
/*40*/ PAIR_UV | CYCLES_REG | SRCDEP_EAX | DSTDEP_EAX, PAIR_UV | CYCLES_REG | SRCDEP_ECX | DSTDEP_ECX, PAIR_UV | CYCLES_REG | SRCDEP_EDX | DSTDEP_EDX, PAIR_UV | CYCLES_REG | SRCDEP_EBX | DSTDEP_EBX,
@@ -428,7 +487,7 @@ static uint32_t opcode_timings_mod3[256] =
PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(2), PAIR_UV | CYCLES_REG, INVALID
};
static uint32_t opcode_timings_0f[256] =
static uint64_t opcode_timings_0f[256] =
{
/*00*/ PAIR_NP | CYCLES(20), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(10),
INVALID, PAIR_NP | CYCLES(195), PAIR_NP | CYCLES(7), INVALID,
@@ -510,7 +569,7 @@ static uint32_t opcode_timings_0f[256] =
PAIR_U | CYCLES_RM, PAIR_U | CYCLES_RM, PAIR_U | CYCLES_RM, INVALID,
PAIR_U | CYCLES_RM, PAIR_U | CYCLES_RM, PAIR_U | CYCLES_RM, INVALID,
};
static uint32_t opcode_timings_0f_mod3[256] =
static uint64_t opcode_timings_0f_mod3[256] =
{
/*00*/ PAIR_NP | CYCLES(20), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(10),
INVALID, PAIR_NP | CYCLES(195), PAIR_NP | CYCLES(7), INVALID,
@@ -593,53 +652,53 @@ static uint32_t opcode_timings_0f_mod3[256] =
PAIR_UV | CYCLES_REG, PAIR_UV | CYCLES_REG, PAIR_UV | CYCLES_REG, INVALID,
};
static uint32_t opcode_timings_shift[8] =
static uint64_t opcode_timings_shift[8] =
{
PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW,
PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW, PAIR_U | CYCLES_RMW,
};
static uint32_t opcode_timings_shift_mod3[8] =
static uint64_t opcode_timings_shift_mod3[8] =
{
PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM,
PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM, PAIR_U | CYCLES_REG | DSTDEP_RM,
};
static uint32_t opcode_timings_f6[8] =
static uint64_t opcode_timings_f6[8] =
{
/* TST NOT NEG*/
PAIR_UV | CYCLES_RM, INVALID, PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3),
/* MUL IMUL DIV IDIV*/
PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(17), PAIR_NP | CYCLES(22)
};
static uint32_t opcode_timings_f6_mod3[8] =
static uint64_t opcode_timings_f6_mod3[8] =
{
/* TST NOT NEG*/
PAIR_UV | CYCLES_REG | SRCDEP_RM, INVALID, PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3),
/* MUL IMUL DIV IDIV*/
PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(11), PAIR_NP | CYCLES(17), PAIR_NP | CYCLES(22)
};
static uint32_t opcode_timings_f7[8] =
static uint64_t opcode_timings_f7[8] =
{
/* TST NOT NEG*/
PAIR_UV | CYCLES_RM, INVALID, PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3),
/* MUL IMUL DIV IDIV*/
PAIR_NP | CYCLES_MULTI(11,10), PAIR_NP | CYCLES_MULTI(11,10), PAIR_NP | CYCLES_MULTI(25,41), PAIR_NP | CYCLES_MULTI(30,46)
};
static uint32_t opcode_timings_f7_mod3[8] =
static uint64_t opcode_timings_f7_mod3[8] =
{
/* TST NOT NEG*/
PAIR_UV | CYCLES_REG | SRCDEP_RM, INVALID, PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3),
/* MUL IMUL DIV IDIV*/
PAIR_NP | CYCLES_MULTI(11,10), PAIR_NP | CYCLES_MULTI(11,10), PAIR_NP | CYCLES_MULTI(25,41), PAIR_NP | CYCLES_MULTI(30,46)
};
static uint32_t opcode_timings_ff[8] =
static uint64_t opcode_timings_ff[8] =
{
/* INC DEC CALL CALL far*/
PAIR_UV | CYCLES_RMW, PAIR_UV | CYCLES_RMW, PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(0),
/* JMP JMP far PUSH*/
PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(0), PAIR_NP | CYCLES(2), INVALID
};
static uint32_t opcode_timings_ff_mod3[8] =
static uint64_t opcode_timings_ff_mod3[8] =
{
/* INC DEC CALL CALL far*/
PAIR_UV | CYCLES_REG | SRCDEP_RM | DSTDEP_RM, PAIR_UV | CYCLES_REG | SRCDEP_RM | DSTDEP_RM, PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(0),
@@ -647,82 +706,83 @@ static uint32_t opcode_timings_ff_mod3[8] =
PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(0), PAIR_NP | CYCLES(2), INVALID
};
static uint32_t opcode_timings_d8[8] =
static uint64_t opcode_timings_d8[8] =
{
/* FADDs FMULs FCOMs FCOMPs*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1),
/* FSUBs FSUBRs FDIVs FDIVRs*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(39), PAIR_FX | CYCLES(39)
/* FADDs FMULs FCOMs FCOMPs*/
PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_CYCLES(1,0,0), PAIR_FX | FPU_POP | FPU_READ_ST0 | FPU_CYCLES(1,0,0),
/* FSUBs FSUBRs FDIVs FDIVRs*/
PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(39,38,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(39,38,2)
};
static uint32_t opcode_timings_d8_mod3[8] =
static uint64_t opcode_timings_d8_mod3[8] =
{
/* FADD FMUL FCOM FCOMP*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1),
/* FSUB FSUBR FDIV FDIVR*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(39), PAIR_FX | CYCLES(39)
/* FADD FMUL FCOM FCOMP*/
PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_POP | FPU_READ_ST0 | FPU_READ_STREG | FPU_CYCLES(1,0,0),
/* FSUB FSUBR FDIV FDIVR*/
PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(39,38,2), PAIR_FX | FPU_RW_ST0 | FPU_READ_STREG | FPU_CYCLES(39,38,2)
};
static uint32_t opcode_timings_d9[8] =
static uint64_t opcode_timings_d9[8] =
{
/* FLDs FSTs FSTPs*/
PAIR_FX | CYCLES(1), INVALID, PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(2),
/* FLDENV FLDCW FSTENV FSTCW*/
PAIR_NP | CYCLES(32), PAIR_NP | CYCLES(7), PAIR_NP | CYCLES(48), PAIR_NP | CYCLES(2)
/* FLDs FSTs FSTPs*/
PAIR_FX | FPU_PUSH | FPU_CYCLES(1,0,0), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(2,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(2,0,0),
/* FLDENV FLDCW FSTENV FSTCW*/
PAIR_NP | FPU_CYCLES(32,0,0), PAIR_NP | FPU_CYCLES(8,0,0), PAIR_NP | FPU_CYCLES(48,0,0), PAIR_NP | FPU_CYCLES(2,0,0)
};
static uint32_t opcode_timings_d9_mod3[64] =
static uint64_t opcode_timings_d9_mod3[64] =
{
/*FLD*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1),
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1),
PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0),
PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_FX | FPU_PUSH | FPU_READ_STREG | FPU_CYCLES(1,0,0),
/*FXCH*/
PAIR_V | CYCLES(0), PAIR_V | CYCLES(0), PAIR_V | CYCLES(0), PAIR_V | CYCLES(0),
PAIR_V | CYCLES(0), PAIR_V | CYCLES(0), PAIR_V | CYCLES(0), PAIR_V | CYCLES(0),
PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0),
PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0), PAIR_FXCH | FPU_FXCH | CYCLES(0),
/*FNOP*/
PAIR_NP | CYCLES(3), INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
PAIR_NP | FPU_CYCLES(3,0,0), INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
/*FSTP*/
PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1),
PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1),
/* opFCHS opFABS*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(3), INVALID, INVALID,
/* opFTST opFXAM*/
PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(21), INVALID, INVALID,
/* opFLD1 opFLDL2T opFLDL2E opFLDPI*/
PAIR_NP | CYCLES(2), CYCLES(3), PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3),
/* opFLDEG2 opFLDLN2 opFLDZ*/
PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(2), INVALID,
/* opF2XM1 opFYL2X opFPTAN opFPATAN*/
PAIR_NP | CYCLES(13), PAIR_NP | CYCLES(22), PAIR_NP | CYCLES(100), PAIR_NP | CYCLES(100),
/* opFDECSTP opFINCSTP,*/
INVALID, INVALID, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(1),
/* opFPREM opFSQRT opFSINCOS*/
PAIR_NP | CYCLES(70), INVALID, PAIR_NP | CYCLES(70), PAIR_NP | CYCLES(50),
/* opFRNDINT opFSCALE opFSIN opFCOS*/
PAIR_NP | CYCLES(9), PAIR_NP | CYCLES(20), PAIR_NP | CYCLES(50), PAIR_NP | CYCLES(50)
PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0),
PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0),
/* opFCHS opFABS*/
PAIR_FX | FPU_CYCLES(1,0,0), PAIR_FX | FPU_CYCLES(1,0,0), INVALID, INVALID,
/* opFTST opFXAM*/
PAIR_NP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_CYCLES(21,4,0), INVALID, INVALID,
/* opFLD1 opFLDL2T opFLDL2E opFLDPI*/
PAIR_NP | FPU_PUSH | FPU_CYCLES(2,0,0), PAIR_NP | FPU_PUSH | FPU_CYCLES(5,2,2), PAIR_NP | FPU_PUSH | FPU_CYCLES(5,2,2), PAIR_NP | FPU_PUSH | FPU_CYCLES(5,2,2),
/* opFLDEG2 opFLDLN2 opFLDZ*/
PAIR_NP | FPU_PUSH | FPU_CYCLES(5,2,2), PAIR_NP | FPU_PUSH | FPU_CYCLES(5,2,2), PAIR_NP | FPU_PUSH | FPU_CYCLES(2,0,0), INVALID,
/* opF2XM1 opFYL2X opFPTAN opFPATAN*/
PAIR_NP | FPU_CYCLES(53,2,2), PAIR_NP | FPU_CYCLES(103,2,2), PAIR_NP | FPU_CYCLES(120,36,0), PAIR_NP | FPU_CYCLES(112,2,2),
/* opFDECSTP opFINCSTP,*/
INVALID, INVALID, PAIR_NP | FPU_CYCLES(2,0,0), PAIR_NP | FPU_CYCLES(2,0,0),
/* opFPREM opFSQRT opFSINCOS*/
PAIR_NP | FPU_CYCLES(64,2,2), INVALID, PAIR_NP | FPU_CYCLES(70,69,2), PAIR_NP | FPU_CYCLES(89,2,2),
/* opFRNDINT opFSCALE opFSIN opFCOS*/
PAIR_NP | FPU_CYCLES(9,0,0), PAIR_NP | FPU_CYCLES(20,5,0), PAIR_NP | FPU_CYCLES(65,2,2), PAIR_NP | FPU_CYCLES(65,2,2)
};
static uint32_t opcode_timings_da[8] =
static uint64_t opcode_timings_da[8] =
{
/* FIADDl FIMULl FICOMl FICOMPl*/
PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4),
/* FISUBl FISUBRl FIDIVl FIDIVRl*/
PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(42), PAIR_NP | CYCLES(42)
/* FIADDl FIMULl FICOMl FICOMPl*/
PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(4,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(4,0,0),
/* FISUBl FISUBRl FIDIVl FIDIVRl*/
PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(42,38,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(42,38,2)
};
static uint32_t opcode_timings_da_mod3[8] =
static uint64_t opcode_timings_da_mod3[8] =
{
INVALID, INVALID, INVALID, INVALID,
INVALID, PAIR_NP | CYCLES(5), INVALID, INVALID
INVALID, INVALID, INVALID, INVALID,
/* FCOMPP*/
INVALID, PAIR_NP | FPU_POP2 | FPU_CYCLES(1,0,0), INVALID, INVALID
};
static uint32_t opcode_timings_db[8] =
static uint64_t opcode_timings_db[8] =
{
/* FLDil FSTil FSTPil*/
PAIR_NP | CYCLES(1), INVALID, PAIR_NP | CYCLES(6), PAIR_NP | CYCLES(6),
/* FLDe FSTPe*/
INVALID, PAIR_NP | CYCLES(3), INVALID, PAIR_NP | CYCLES(3)
/* FLDil FSTil FSTPil*/
PAIR_NP | FPU_PUSH | FPU_CYCLES(3,2,2), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(6,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(6,0,0),
/* FLDe FSTPe*/
INVALID, PAIR_NP | FPU_PUSH | FPU_CYCLES(3,0,0), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(3,0,0)
};
static uint32_t opcode_timings_db_mod3[64] =
static uint64_t opcode_timings_db_mod3[64] =
{
INVALID, INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
@@ -736,10 +796,10 @@ static uint32_t opcode_timings_db_mod3[64] =
INVALID, INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
/* opFNOP opFCLEX opFINIT*/
INVALID, PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(7), PAIR_NP | CYCLES(17),
/* opFNOP opFNOP*/
PAIR_NP | CYCLES(3), PAIR_NP | CYCLES(3), INVALID, INVALID,
/* opFNOP opFCLEX opFINIT*/
INVALID, PAIR_NP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_CYCLES(7,0,0), PAIR_NP | FPU_CYCLES(17,0,0),
/* opFNOP opFNOP*/
PAIR_NP | FPU_CYCLES(1,0,0), PAIR_NP | FPU_CYCLES(1,0,0), INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
@@ -751,76 +811,84 @@ static uint32_t opcode_timings_db_mod3[64] =
INVALID, INVALID, INVALID, INVALID,
};
static uint32_t opcode_timings_dc[8] =
static uint64_t opcode_timings_dc[8] =
{
/* FADDd FMULd FCOMd FCOMPd*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1),
/* FSUBd FSUBRd FDIVd FDIVRd*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(39), PAIR_FX | CYCLES(39)
/* FADDd FMULd FCOMd FCOMPd*/
PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_CYCLES(1,0,0), PAIR_FX | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(1,0,0),
/* FSUBd FSUBRd FDIVd FDIVRd*/
PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(3,2,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(39,38,2), PAIR_FX | FPU_RW_ST0 | FPU_CYCLES(39,38,2)
};
static uint32_t opcode_timings_dc_mod3[8] =
static uint64_t opcode_timings_dc_mod3[8] =
{
/* opFADDr opFMULr*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), INVALID, INVALID,
/* opFSUBRr opFSUBr opFDIVRr opFDIVr*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(39), PAIR_FX | CYCLES(39)
/* opFADDr opFMULr*/
PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(3,2,2), INVALID, INVALID,
/* opFSUBRr opFSUBr opFDIVRr opFDIVr*/
PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(39,38,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_CYCLES(39,38,2)
};
static uint32_t opcode_timings_dd[8] =
static uint64_t opcode_timings_dd[8] =
{
/* FLDd FSTd FSTPd*/
PAIR_FX | CYCLES(1), INVALID, PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(2),
/* FRSTOR FSAVE FSTSW*/
PAIR_NP | CYCLES(70), INVALID, PAIR_NP | CYCLES(127), PAIR_NP | CYCLES(2)
/* FLDd FSTd FSTPd*/
PAIR_FX | FPU_PUSH | FPU_CYCLES(1,0,0), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(2,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(2,0,0),
/* FRSTOR FSAVE FSTSW*/
PAIR_NP | FPU_CYCLES(70,0,0), INVALID, PAIR_NP | FPU_CYCLES(127,0,0), PAIR_NP | FPU_CYCLES(6,0,0)
};
static uint32_t opcode_timings_dd_mod3[8] =
static uint64_t opcode_timings_dd_mod3[8] =
{
/* FFFREE FST FSTP*/
PAIR_NP | CYCLES(3), INVALID, PAIR_NP | CYCLES(2), PAIR_NP | CYCLES(2),
/* FUCOM FUCOMP*/
PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), INVALID, INVALID
/* FFFREE FST FSTP*/
PAIR_NP | FPU_CYCLES(2,0,0), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_WRITE_STREG | FPU_POP | FPU_CYCLES(1,0,0),
/* FUCOM FUCOMP*/
PAIR_NP | FPU_READ_ST0 | FPU_READ_STREG | FPU_CYCLES(1,0,0), PAIR_NP | FPU_READ_ST0 | FPU_READ_STREG | FPU_POP | FPU_CYCLES(1,0,0), INVALID, INVALID
};
static uint32_t opcode_timings_de[8] =
static uint64_t opcode_timings_de[8] =
{
/* FIADDw FIMULw FICOMw FICOMPw*/
PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4),
/* FISUBw FISUBRw FIDIVw FIDIVRw*/
PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(4), PAIR_NP | CYCLES(42), PAIR_NP | CYCLES(42)
/* FIADDw FIMULw FICOMw FICOMPw*/
PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(4,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(4,0,0),
/* FISUBw FISUBRw FIDIVw FIDIVRw*/
PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(6,2,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(42,38,2), PAIR_NP | FPU_RW_ST0 | FPU_CYCLES(42,38,2)
};
static uint32_t opcode_timings_de_mod3[8] =
static uint64_t opcode_timings_de_mod3[8] =
{
/* FADD FMUL FCOMPP*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), INVALID, PAIR_FX | CYCLES(1),
/* FSUB FSUBR FDIV FDIVR*/
PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(1), PAIR_FX | CYCLES(39), PAIR_FX | CYCLES(39)
/* FADDP FMULP FCOMPP*/
PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(3,2,2), INVALID, PAIR_FX | FPU_READ_ST0 | FPU_READ_ST1 | FPU_POP2 | FPU_CYCLES(1,0,0),
/* FSUBP FSUBRP FDIVP FDIVRP*/
PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(3,2,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(39,38,2), PAIR_FX | FPU_READ_ST0 | FPU_RW_STREG | FPU_POP | FPU_CYCLES(39,38,2)
};
static uint32_t opcode_timings_df[8] =
static uint64_t opcode_timings_df[8] =
{
/* FILDiw FISTiw FISTPiw*/
PAIR_NP | CYCLES(1), INVALID, PAIR_NP | CYCLES(6), PAIR_NP | CYCLES(6),
/* FILDiq FBSTP FISTPiq*/
INVALID, PAIR_NP | CYCLES(1), PAIR_NP | CYCLES(148), PAIR_NP | CYCLES(6)
/* FILDiw FISTiw FISTPiw*/
PAIR_NP | FPU_PUSH | FPU_CYCLES(3,2,2), INVALID, PAIR_NP | FPU_READ_ST0 | FPU_CYCLES(6,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(6,0,0),
/* FILDiq FBSTP FISTPiq*/
INVALID, PAIR_NP | FPU_PUSH | FPU_CYCLES(3,2,2), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(148,0,0), PAIR_NP | FPU_READ_ST0 | FPU_POP | FPU_CYCLES(6,0,0)
};
static uint32_t opcode_timings_df_mod3[8] =
static uint64_t opcode_timings_df_mod3[8] =
{
INVALID, INVALID, INVALID, INVALID,
INVALID, INVALID, INVALID, INVALID,
/* FSTSW AX*/
PAIR_NP | CYCLES(2), INVALID, INVALID, INVALID
PAIR_NP | FPU_CYCLES(6,0,0), INVALID, INVALID, INVALID
};
static uint32_t opcode_timings_8x[8] =
static uint64_t opcode_timings_81[8] =
{
PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG,
PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RMW | SRCDEP_REG, PAIR_UV | CYCLES_RM | SRCDEP_REG
PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632,
PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM1632, PAIR_UV | CYCLES_RM | SRCDEP_REG | CYCLES_IMM1632
};
static uint64_t opcode_timings_8x[8] =
{
PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8,
PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RMW | SRCDEP_REG | CYCLES_IMM8, PAIR_UV | CYCLES_RM | SRCDEP_REG | CYCLES_IMM8
};
static int decode_delay;
static int decode_delay, decode_delay_offset;
static uint8_t last_prefix;
static int prefixes;
static inline int COUNT(uint32_t c, int op_32)
static inline int COUNT(uint64_t c, int op_32)
{
if ((c & PAIR_FPU) && !(c & FPU_FXCH))
return FPU_I_LATENCY(c);
if (c & CYCLES_HAS_MULTI)
{
if (op_32 & 0x100)
@@ -831,6 +899,10 @@ static inline int COUNT(uint32_t c, int op_32)
return c & 0xffff;
if ((c & PAIR_MASK) == PAIR_FX)
return c & 0xffff;
if ((c & PAIR_MASK) == PAIR_FXCH)
return c & 0xffff;
if ((c & PAIR_UV) && !(c & PAIR_FPU))
c &= 3;
switch (c & CYCLES_MASK)
{
case CYCLES_REG:
@@ -843,23 +915,136 @@ static inline int COUNT(uint32_t c, int op_32)
return cpu_hasMMX ? 1 : 2;
}
fatal("Illegal COUNT %08x\n", c);
fatal("Illegal COUNT %016llx\n", c);
return c;
}
static int codegen_fpu_latencies(uint64_t timings, int reg)
{
int latency = fpu_latency;
if ((timings & FPU_RW_ST0) && fpu_st_latency[0] && fpu_st_latency[0] > latency)
latency = fpu_st_latency[0];
if ((timings & FPU_RW_ST1) && fpu_st_latency[1] && fpu_st_latency[1] > latency)
latency = fpu_st_latency[1];
if ((timings & FPU_RW_STREG) && fpu_st_latency[reg] && fpu_st_latency[reg] > latency)
latency = fpu_st_latency[reg];
return latency;
}
#define SUB_AND_CLAMP(latency, count) \
latency -= count; \
if (latency < 0) \
latency = 0
static void codegen_fpu_latency_clock(int count)
{
SUB_AND_CLAMP(fpu_latency, count);
SUB_AND_CLAMP(fpu_st_latency[0], count);
SUB_AND_CLAMP(fpu_st_latency[1], count);
SUB_AND_CLAMP(fpu_st_latency[2], count);
SUB_AND_CLAMP(fpu_st_latency[3], count);
SUB_AND_CLAMP(fpu_st_latency[4], count);
SUB_AND_CLAMP(fpu_st_latency[5], count);
SUB_AND_CLAMP(fpu_st_latency[6], count);
SUB_AND_CLAMP(fpu_st_latency[7], count);
}
static inline int codegen_timing_has_displacement(uint32_t fetchdat, int op_32)
{
if (op_32 & 0x200)
{
if ((fetchdat & 7) == 4 && (fetchdat & 0xc0) != 0xc0)
{
/*Has SIB*/
if ((fetchdat & 0xc0) == 0x40 || (fetchdat & 0xc0) == 0x80 || (fetchdat & 0x700) == 0x500)
return 1;
}
else
{
if ((fetchdat & 0xc0) == 0x40 || (fetchdat & 0xc0) == 0x80 || (fetchdat & 0xc7) == 0x05)
return 1;
}
}
else
{
if ((fetchdat & 0xc0) == 0x40 || (fetchdat & 0xc0) == 0x80 || (fetchdat & 0xc7) == 0x06)
return 1;
}
return 0;
}
/*The instruction is only of interest here if it's longer than 7 bytes, as that's the
limit on Pentium MMX parallel decoding*/
static inline int codegen_timing_instr_length(uint64_t timing, uint32_t fetchdat, int op_32)
{
int len = prefixes;
if ((timing & CYCLES_MASK) == CYCLES_RM || (timing & CYCLES_MASK) == CYCLES_RMW)
{
len += 2; /*Opcode + ModR/M*/
if ((timing & CYCLES_HASIMM) == CYCLES_IMM8)
len++;
if ((timing & CYCLES_HASIMM) == CYCLES_IMM1632)
len += (op_32 & 0x100) ? 4 : 2;
if (op_32 & 0x200)
{
if ((fetchdat & 7) == 4 && (fetchdat & 0xc0) != 0xc0)
{
/* Has SIB*/
len++;
if ((fetchdat & 0xc0) == 0x40)
len++;
else if ((fetchdat & 0xc0) == 0x80)
len += 4;
else if ((fetchdat & 0x700) == 0x500)
len += 4;
}
else
{
if ((fetchdat & 0xc0) == 0x40)
len++;
else if ((fetchdat & 0xc0) == 0x80)
len += 4;
else if ((fetchdat & 0xc7) == 0x05)
len += 4;
}
}
else
{
if ((fetchdat & 0xc0) == 0x40)
len++;
else if ((fetchdat & 0xc0) == 0x80)
len += 2;
else if ((fetchdat & 0xc7) == 0x06)
len += 2;
}
}
return len;
}
void codegen_timing_pentium_block_start()
{
u_pipe_full = decode_delay = 0;
u_pipe_full = decode_delay = decode_delay_offset = 0;
}
void codegen_timing_pentium_start()
{
last_prefix = 0;
prefixes = 0;
}
void codegen_timing_pentium_prefix(uint8_t prefix, uint32_t fetchdat)
{
prefixes++;
if ((prefix & 0xf8) == 0xd8)
{
last_prefix = prefix;
return;
}
if (cpu_hasMMX && prefix == 0x0f)
{
/*On Pentium MMX 0fh prefix is 'free'*/
@@ -869,7 +1054,7 @@ void codegen_timing_pentium_prefix(uint8_t prefix, uint32_t fetchdat)
if (cpu_hasMMX && (prefix == 0x66 || prefix == 0x67))
{
/*On Pentium MMX 66h and 67h prefixes take 2 clocks*/
decode_delay += 2;
decode_delay_offset += 2;
last_prefix = prefix;
return;
}
@@ -881,13 +1066,86 @@ void codegen_timing_pentium_prefix(uint8_t prefix, uint32_t fetchdat)
}
/*On Pentium all prefixes take 1 cycle to decode. Decode may be shadowed
by execution of previous instructions*/
decode_delay++;
decode_delay_offset++;
last_prefix = prefix;
}
static void codegen_instruction(uint64_t *timings, uint8_t opcode, uint32_t fetchdat, int decode_delay_offset, int op_32)
{
int instr_cycles, latency = 0;
if ((timings[opcode] & PAIR_FPU) && !(timings[opcode] & FPU_FXCH))
instr_cycles = latency = codegen_fpu_latencies(timings[opcode], fetchdat & 7);
else
{
instr_cycles = 0;
}
if ((decode_delay + decode_delay_offset) > 0)
codegen_fpu_latency_clock(decode_delay + decode_delay_offset + instr_cycles);
else
codegen_fpu_latency_clock(instr_cycles);
instr_cycles += COUNT(timings[opcode], op_32);
if ((decode_delay + decode_delay_offset) > 0)
codegen_block_cycles += instr_cycles + decode_delay + decode_delay_offset;
else
codegen_block_cycles += instr_cycles;
decode_delay = (-instr_cycles) + 1;
if (timings[opcode] & FPU_POP)
{
int c;
for (c = 0; c < 7; c++)
fpu_st_latency[c] = fpu_st_latency[c+1];
fpu_st_latency[7] = 0;
}
if (timings[opcode] & FPU_POP2)
{
int c;
for (c = 0; c < 6; c++)
fpu_st_latency[c] = fpu_st_latency[c+2];
fpu_st_latency[6] = fpu_st_latency[7] = 0;
}
if ((timings[opcode] & PAIR_FPU) && !(timings[opcode] & FPU_FXCH))
{
fpu_latency = FPU_F_LATENCY(timings[opcode]);
}
if (timings[opcode] & FPU_PUSH)
{
int c;
for (c = 0; c < 7; c++)
fpu_st_latency[c+1] = fpu_st_latency[c];
fpu_st_latency[0] = 0;
}
if (timings[opcode] & FPU_WRITE_ST0)
{
fpu_st_latency[0] = FPU_RESULT_LATENCY(timings[opcode]);
}
if (timings[opcode] & FPU_WRITE_ST1)
{
fpu_st_latency[1] = FPU_RESULT_LATENCY(timings[opcode]);
}
if (timings[opcode] & FPU_WRITE_STREG)
{
int reg = fetchdat & 7;
if (timings[opcode] & FPU_POP)
reg--;
if (reg >= 0 &&
!(reg == 0 && (timings[opcode] & FPU_WRITE_ST0)) &&
!(reg == 1 && (timings[opcode] & FPU_WRITE_ST1)))
{
fpu_st_latency[reg] = FPU_RESULT_LATENCY(timings[opcode]);
}
}
}
void codegen_timing_pentium_opcode(uint8_t opcode, uint32_t fetchdat, int op_32)
{
uint32_t *timings;
uint64_t *timings;
int mod3 = ((fetchdat & 0xc0) == 0xc0);
int bit8 = !(opcode & 1);
@@ -933,11 +1191,16 @@ void codegen_timing_pentium_opcode(uint8_t opcode, uint32_t fetchdat, int op_32)
default:
switch (opcode)
{
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x80: case 0x82: case 0x83:
timings = mod3 ? opcode_timings_mod3 : opcode_timings_8x;
if (!mod3)
opcode = (fetchdat >> 3) & 7;
break;
case 0x81:
timings = mod3 ? opcode_timings_mod3 : opcode_timings_81;
if (!mod3)
opcode = (fetchdat >> 3) & 7;
break;
case 0xc0: case 0xc1: case 0xd0: case 0xd1: case 0xd2: case 0xd3:
timings = mod3 ? opcode_timings_shift_mod3 : opcode_timings_shift;
@@ -963,9 +1226,6 @@ void codegen_timing_pentium_opcode(uint8_t opcode, uint32_t fetchdat, int op_32)
}
}
if (decode_delay < 0)
decode_delay = 0;
if (u_pipe_full)
{
uint8_t regmask = get_srcdep_mask(timings[opcode], fetchdat, bit8);
@@ -977,49 +1237,92 @@ void codegen_timing_pentium_opcode(uint8_t opcode, uint32_t fetchdat, int op_32)
if ((timings[opcode] & PAIR_MASK) == PAIR_FXCH &&
(u_pipe_timings[u_pipe_opcode] & PAIR_MASK) != PAIR_FX)
goto nopair;
if ((timings[opcode] & PAIR_V) && !(u_pipe_regmask & regmask) && !decode_delay)
{
int t1 = u_pipe_timings[u_pipe_opcode] & CYCLES_MASK;
int t2 = timings[opcode] & CYCLES_MASK;
int t_pair;
if (t1 < 0 || t2 < 0 || t1 > CYCLES_BRANCH || t2 > CYCLES_BRANCH)
fatal("Pair out of range\n");
t_pair = pair_timings[t1][t2];
if (t_pair < 1)
fatal("Illegal pair timings : t1=%i t2=%i u_opcode=%02x v_opcode=%02x\n", t1, t2, u_pipe_opcode, opcode);
codegen_block_cycles += t_pair;
decode_delay = (-t_pair) + 1;
/*Instruction can pair with previous*/
if ((u_pipe_timings[u_pipe_opcode] & PAIR_MASK) == PAIR_FX &&
(timings[opcode] & PAIR_MASK) == PAIR_FXCH)
{
int temp;
codegen_instruction(u_pipe_timings, u_pipe_opcode, u_pipe_fetchdat, u_pipe_decode_delay_offset, u_pipe_op_32);
temp = fpu_st_latency[fetchdat & 7];
fpu_st_latency[fetchdat & 7] = fpu_st_latency[0];
fpu_st_latency[0] = temp;
u_pipe_full = 0;
decode_delay_offset = 0;
return;
}
if ((timings[opcode] & PAIR_V) && !(u_pipe_regmask & regmask) && (decode_delay+decode_delay_offset+u_pipe_decode_delay_offset) <= 0)
{
int has_displacement;
if (timings[opcode] & CYCLES_HASIMM)
has_displacement = codegen_timing_has_displacement(fetchdat, op_32);
else
has_displacement = 0;
if (!has_displacement && (!cpu_hasMMX || codegen_timing_instr_length(timings[opcode], fetchdat, op_32) <= 7))
{
int t1 = u_pipe_timings[u_pipe_opcode] & CYCLES_MASK;
int t2 = timings[opcode] & CYCLES_MASK;
int t_pair;
uint64_t temp_timing;
if (!(u_pipe_timings[u_pipe_opcode] & PAIR_FPU))
t1 &= 3;
if (!(timings[opcode] & PAIR_FPU))
t2 &= 3;
if (t1 < 0 || t2 < 0 || t1 > CYCLES_BRANCH || t2 > CYCLES_BRANCH)
fatal("Pair out of range\n");
t_pair = pair_timings[t1][t2];
if (t_pair < 1)
fatal("Illegal pair timings : t1=%i t2=%i u_opcode=%02x v_opcode=%02x\n", t1, t2, u_pipe_opcode, opcode);
/*Instruction can pair with previous*/
temp_timing = t_pair;
codegen_instruction(&temp_timing, 0, 0, 0, 0);
u_pipe_full = 0;
decode_delay_offset = 0;
return;
}
}
nopair:
/*Instruction can not pair with previous*/
/*Run previous now*/
codegen_block_cycles += COUNT(u_pipe_timings[u_pipe_opcode], u_pipe_op_32) + decode_delay;
decode_delay = (-COUNT(u_pipe_timings[u_pipe_opcode], u_pipe_op_32)) + 1;
codegen_instruction(u_pipe_timings, u_pipe_opcode, u_pipe_fetchdat, u_pipe_decode_delay_offset, u_pipe_op_32);
u_pipe_full = 0;
}
if ((timings[opcode] & PAIR_U) && !decode_delay)
if ((timings[opcode] & PAIR_U) && (decode_delay + decode_delay_offset) <= 0)
{
/*Instruction might pair with next*/
u_pipe_full = 1;
u_pipe_opcode = opcode;
u_pipe_timings = timings;
u_pipe_op_32 = op_32;
u_pipe_regmask = get_dstdep_mask(timings[opcode], fetchdat, bit8);
return;
int has_displacement;
if (timings[opcode] & CYCLES_HASIMM)
has_displacement = codegen_timing_has_displacement(fetchdat, op_32);
else
has_displacement = 0;
if ((!has_displacement || cpu_hasMMX) && (!cpu_hasMMX || codegen_timing_instr_length(timings[opcode], fetchdat, op_32) <= 7))
{
/*Instruction might pair with next*/
u_pipe_full = 1;
u_pipe_opcode = opcode;
u_pipe_timings = timings;
u_pipe_op_32 = op_32;
u_pipe_regmask = get_dstdep_mask(timings[opcode], fetchdat, bit8);
u_pipe_fetchdat = fetchdat;
u_pipe_decode_delay_offset = decode_delay_offset;
decode_delay_offset = 0;
return;
}
}
/*Instruction can not pair and must run now*/
codegen_block_cycles += COUNT(timings[opcode], op_32) + decode_delay;
decode_delay = (-COUNT(timings[opcode], op_32)) + 1;
codegen_instruction(timings, opcode, fetchdat, decode_delay_offset, op_32);
decode_delay_offset = 0;
}
void codegen_timing_pentium_block_end()
@@ -1027,7 +1330,7 @@ void codegen_timing_pentium_block_end()
if (u_pipe_full)
{
/*Run previous now*/
codegen_block_cycles += COUNT(u_pipe_timings[u_pipe_opcode], u_pipe_op_32) + decode_delay;
codegen_block_cycles += COUNT(u_pipe_timings[u_pipe_opcode], u_pipe_op_32) + decode_delay + decode_delay_offset;
u_pipe_full = 0;
}
}

View File

@@ -62,7 +62,7 @@ endif
#########################################################################
# Nothing should need changing from here on.. #
#########################################################################
VPATH = . cpu sound sound/resid-fp video lzf network network/slirp win
VPATH = . cpu sound sound/munt sound/munt/c_interface sound/munt/sha1 sound/munt/srchelper sound/resid-fp video lzf network network/slirp win
PLAT = win/
ifeq ($(X64), y)
CPP = g++.exe -m64
@@ -76,9 +76,17 @@ WINDRES = windres.exe
OPTS = -DWIN32 -I$(PLAT) $(EXTRAS) $(STUFF)
ifeq ($(X64), y)
DFLAGS =
ifeq ($(OPTIM), y)
DFLAGS = -march=native
else
DFLAGS =
endif
else
DFLAGS = -march=i686
ifeq ($(OPTIM), y)
DFLAGS = -march=native
else
DFLAGS = -march=i686
endif
endif
ifeq ($(DEBUG), y)
DFLAGS += -ggdb -DDEBUG
@@ -175,7 +183,13 @@ SNDOBJ = sound.o \
wave6581_P_T.o wave6581_PS_.o wave6581_PST.o \
wave8580__ST.o wave8580_P_T.o wave8580_PS_.o \
wave8580_PST.o wave.o \
Analog.o BReverbModel.o File.o FileStream.o LA32Ramp.o \
LA32FloatWaveGenerator.o LA32WaveGenerator.o \
MidiStreamParser.o Part.o Partial.o PartialManager.o \
Poly.o ROMInfo.o Synth.o Tables.o TVA.o TVF.o TVP.o \
sha1.o c_interface.o \
dbopl.o nukedopl.o openal.o \
midi.o midi_mt32.o midi_system.o \
snd_speaker.o snd_ps1.o snd_pssj.o \
snd_adlib.o snd_adlibgold.o snd_ad1848.o \
snd_sb.o snd_sb_dsp.o snd_cms.o snd_dbopl.o \
@@ -222,7 +236,7 @@ LZFOBJ = lzf_c.o lzf_d.o
LIBS = -lddraw -ldinput8 -ldxguid -ld3d9 -ld3dx9 -lopenal.dll \
-mwindows -lcomctl32 -lwinmm -lwsock32 -liphlpapi -lpsapi \
-static-libstdc++ -static -lstdc++ -static-libgcc -static -lgcc
-static -static-libgcc -static-libstdc++ -lstdc++ -lgcc
# Build rules.

View File

@@ -243,7 +243,7 @@ nelog(int lvl, const char *fmt, ...)
static void
nic_interrupt(nic_t *dev, int set)
{
if ((PCI && dev->is_pci) && (dev->base_irq == 0xff)) {
if (PCI && dev->is_pci) {
if (set)
pci_set_irq(dev->card, PCI_INTA);
else

247
src/SOUND/midi.c Normal file
View File

@@ -0,0 +1,247 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "../device.h"
#include "midi.h"
#include "../ibm.h"
#include "../WIN/plat_midi.h"
#include "../WIN/plat_ticks.h"
#include "midi_system.h"
#include "midi_mt32.h"
int midi_device_current = 0;
static int midi_device_last = 0;
typedef struct
{
const char *name;
const char *internal_name;
device_t *device;
} MIDI_DEVICE;
static MIDI_DEVICE devices[] =
{
{"None", "none", NULL},
{SYSTEM_MIDI_NAME, SYSTEM_MIDI_INTERNAL_NAME, &system_midi_device},
{"Roland MT-32 Emulation", "mt32", &mt32_device},
{"", "", NULL}
};
static midi_device_t* m_device = NULL;
int midi_device_available(int card)
{
if (devices[card].device)
return device_available(devices[card].device);
return 1;
}
char *midi_device_getname(int card)
{
return (char *) devices[card].name;
}
device_t *midi_device_getdevice(int card)
{
return devices[card].device;
}
int midi_device_has_config(int card)
{
if (!devices[card].device)
return 0;
return devices[card].device->config ? 1 : 0;
}
char *midi_device_get_internal_name(int card)
{
return (char *) devices[card].internal_name;
}
int midi_device_get_from_internal_name(char *s)
{
int c = 0;
while (strlen(devices[c].internal_name))
{
if (!strcmp(devices[c].internal_name, s))
return c;
c++;
}
return 0;
}
void midi_device_init()
{
if (devices[midi_device_current].device)
device_add(devices[midi_device_current].device);
midi_device_last = midi_device_current;
}
static uint8_t midi_rt_buf[1024];
static uint8_t midi_cmd_buf[1024];
static int midi_cmd_pos = 0;
static int midi_cmd_len = 0;
static uint8_t midi_status = 0;
static unsigned int midi_sysex_start = 0;
static unsigned int midi_sysex_delay = 0;
uint8_t MIDI_evt_len[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x10
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x30
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x70
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x80
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x90
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xa0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xb0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xc0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xd0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xe0
0,2,3,2, 0,0,1,0, 1,0,1,1, 1,0,1,0 // 0xf0
};
static unsigned int midi_pos;
static uint8_t midi_sysex_data[1024+2];
void midi_init(midi_device_t* device)
{
memset(midi_rt_buf, 0, sizeof(midi_rt_buf));
memset(midi_cmd_buf, 0, sizeof(midi_cmd_buf));
midi_cmd_pos = midi_cmd_len = 0;
midi_status = 0;
midi_sysex_start = midi_sysex_delay = 0;
m_device = device;
}
void midi_close()
{
m_device = NULL;
}
void midi_poll()
{
if (m_device && m_device->poll) m_device->poll();
}
void play_msg(uint8_t *msg)
{
if (m_device->play_msg) m_device->play_msg(msg);
}
void play_sysex(uint8_t *sysex, unsigned int len)
{
if (m_device->play_sysex) m_device->play_sysex(sysex, len);
}
#define SYSEX_SIZE 1024
#define RAWBUF 1024
void midi_write(uint8_t val)
{
if (!m_device) return;
if (m_device->write && m_device->write(val)) return;
uint32_t passed_ticks;
if (midi_sysex_start)
{
passed_ticks = get_ticks() - midi_sysex_start;
if (passed_ticks < midi_sysex_delay)
{
delay_ms(midi_sysex_delay - passed_ticks);
}
}
/* Test for a realtime MIDI message */
if (val >= 0xf8)
{
midi_rt_buf[0] = val;
play_msg(midi_rt_buf);
return;
}
/* Test for a active sysex transfer */
if (midi_status == 0xf0)
{
if (!(val & 0x80))
{
if (midi_pos < (SYSEX_SIZE-1)) midi_sysex_data[midi_pos++] = val;
return;
}
else
{
midi_sysex_data[midi_pos++] = 0xf7;
if ((midi_sysex_start) && (midi_pos >= 4) && (midi_pos <= 9) && (midi_sysex_data[1] == 0x411) && (midi_sysex_data[3] == 0x16))
{
/* pclog("MIDI: Skipping invalid MT-32 SysEx MIDI message\n"); */
}
else
{
play_sysex(midi_sysex_data, midi_pos);
if (midi_sysex_start)
{
if (midi_sysex_data[5] == 0x7f)
{
midi_sysex_delay = 290; /* All parameters reset */
}
else if ((midi_sysex_data[5] == 0x10) && (midi_sysex_data[6] == 0x00) && (midi_sysex_data[7] == 0x04))
{
midi_sysex_delay = 145; /* Viking Child */
}
else if ((midi_sysex_data[5] == 0x10) && (midi_sysex_data[6] == 0x00) && (midi_sysex_data[7] == 0x01))
{
midi_sysex_delay = 30; /* Dark Sun 1 */
}
else
midi_sysex_delay = (unsigned int) (((float) (midi_pos) * 1.25f) * 1000.0f / 3125.0f) + 2;
midi_sysex_start = get_ticks();
}
}
}
}
if (val & 0x80)
{
midi_status = val;
midi_cmd_pos = 0;
midi_cmd_len = MIDI_evt_len[val];
if (midi_status == 0xf0)
{
midi_sysex_data[0] = 0xf0;
midi_pos = 1;
}
}
if (midi_cmd_len)
{
midi_cmd_buf[midi_cmd_pos++] = val;
if (midi_cmd_pos >= midi_cmd_len)
{
play_msg(midi_cmd_buf);
midi_cmd_pos = 1;
}
}
}

35
src/SOUND/midi.h Normal file
View File

@@ -0,0 +1,35 @@
extern int midi_device_current;
int midi_device_available(int card);
char *midi_device_getname(int card);
struct device_t *midi_device_getdevice(int card);
int midi_device_has_config(int card);
char *midi_device_get_internal_name(int card);
int midi_device_get_from_internal_name(char *s);
void midi_device_init();
typedef struct midi_device_t
{
void (*play_sysex)(uint8_t *sysex, unsigned int len);
void (*play_msg)(uint8_t *msg);
void (*poll)();
int (*write)(uint8_t val);
} midi_device_t;
void midi_init(midi_device_t* device);
void midi_close();
void midi_write(uint8_t val);
void midi_poll();
#if 0
#ifdef _WIN32
#define SYSTEM_MIDI_NAME "Windows MIDI"
#define SYSTEM_MIDI_INTERNAL_NAME "windows_midi"
#else
#define SYSTEM_MIDI_NAME "System MIDI"
#define SYSTEM_MIDI_INTERNAL_NAME "system_midi"
#endif
#else
#define SYSTEM_MIDI_NAME "System MIDI"
#define SYSTEM_MIDI_INTERNAL_NAME "system_midi"
#endif

323
src/SOUND/midi_mt32.c Normal file
View File

@@ -0,0 +1,323 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "munt/c_interface/c_interface.h"
#include "../WIN/plat_thread.h"
#include "../ibm.h"
#include "../device.h"
#include "../mem.h"
#include "../rom.h"
#include "midi_mt32.h"
#include "midi.h"
#include "sound.h"
extern void givealbuffer_midi(void *buf, uint32_t size);
extern void pclog(const char *format, ...);
extern void al_set_midi(int freq, int buf_size);
static const mt32emu_report_handler_i_v0 handler_v0 = {
/** Returns the actual interface version ID */
NULL, //mt32emu_report_handler_version (*getVersionID)(mt32emu_report_handler_i i);
/** Callback for debug messages, in vprintf() format */
NULL, //void (*printDebug)(void *instance_data, const char *fmt, va_list list);
/** Callbacks for reporting errors */
NULL, //void (*onErrorControlROM)(void *instance_data);
NULL, //void (*onErrorPCMROM)(void *instance_data);
/** Callback for reporting about displaying a new custom message on LCD */
NULL, //void (*showLCDMessage)(void *instance_data, const char *message);
/** Callback for reporting actual processing of a MIDI message */
NULL, //void (*onMIDIMessagePlayed)(void *instance_data);
/**
* Callback for reporting an overflow of the input MIDI queue.
* Returns MT32EMU_BOOL_TRUE if a recovery action was taken
* and yet another attempt to enqueue the MIDI event is desired.
*/
NULL, //mt32emu_boolean (*onMIDIQueueOverflow)(void *instance_data);
/**
* Callback invoked when a System Realtime MIDI message is detected in functions
* mt32emu_parse_stream and mt32emu_play_short_message and the likes.
*/
NULL, //void (*onMIDISystemRealtime)(void *instance_data, mt32emu_bit8u system_realtime);
/** Callbacks for reporting system events */
NULL, //void (*onDeviceReset)(void *instance_data);
NULL, //void (*onDeviceReconfig)(void *instance_data);
/** Callbacks for reporting changes of reverb settings */
NULL, //void (*onNewReverbMode)(void *instance_data, mt32emu_bit8u mode);
NULL, //void (*onNewReverbTime)(void *instance_data, mt32emu_bit8u time);
NULL, //void (*onNewReverbLevel)(void *instance_data, mt32emu_bit8u level);
/** Callbacks for reporting various information */
NULL, //void (*onPolyStateChanged)(void *instance_data, mt32emu_bit8u part_num);
NULL, //void (*onProgramChanged)(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name);
};
static const mt32emu_report_handler_i handler = { &handler_v0 };
static mt32emu_context context = NULL;
static int roms_present = -1;
mt32emu_return_code mt32_check(const char* func, mt32emu_return_code ret, mt32emu_return_code expected)
{
if (ret != expected)
{
pclog("%s() failed, expected %d but returned %d\n", func, expected, ret);
return 0;
}
return 1;
}
int mt32_available()
{
if (roms_present < 0)
roms_present = (rom_present(L"roms/mt32/mt32_control.rom") && rom_present(L"roms/mt32/mt32_pcm.rom"));
return roms_present;
}
static thread_t *thread_h = NULL;
static event_t *event = NULL;
#define RENDER_RATE 30
static uint32_t samplerate = 44100;
static int buf_size = 0;
static float* buffer = NULL;
static int16_t* buffer_int16 = NULL;
static int midi_pos = 0;
void mt32_stream(float* stream, int len)
{
if (context) mt32emu_render_float(context, stream, len);
}
void mt32_stream_int16(int16_t* stream, int len)
{
if (context) mt32emu_render_bit16s(context, stream, len);
}
void mt32_poll()
{
midi_pos++;
if (midi_pos == 48000/RENDER_RATE)
{
midi_pos = 0;
thread_set_event(event);
}
}
extern int soundon;
static void mt32_thread(void *param)
{
while (1)
{
thread_wait_event(event, -1);
if (sound_is_float)
{
memset(buffer, 0, buf_size * sizeof(float));
mt32_stream(buffer, (samplerate/RENDER_RATE));
if (soundon)
givealbuffer_midi(buffer, buf_size);
}
else
{
memset(buffer_int16, 0, buf_size * sizeof(int16_t));
mt32_stream_int16(buffer_int16, (samplerate/RENDER_RATE));
if (soundon)
givealbuffer_midi(buffer_int16, buf_size);
}
}
}
void mt32_msg(uint8_t* val)
{
if (context) mt32_check("mt32emu_play_msg", mt32emu_play_msg(context, *(uint32_t*)val), MT32EMU_RC_OK);
}
void mt32_sysex(uint8_t* data, unsigned int len)
{
if (context) mt32_check("mt32emu_play_sysex", mt32emu_play_sysex(context, data, len), MT32EMU_RC_OK);
}
void* mt32_init()
{
wchar_t s[512];
char fn[512];
context = mt32emu_create_context(handler, NULL);
if (!rom_getfile(L"roms/mt32/mt32_control.rom", s, 512)) return 0;
wcstombs(fn, s, (wcslen(s) << 1) + 2);
if (!mt32_check("mt32emu_add_rom_file", mt32emu_add_rom_file(context, fn), MT32EMU_RC_ADDED_CONTROL_ROM)) return 0;
if (!rom_getfile(L"roms/mt32/mt32_pcm.rom", s, 512)) return 0;
wcstombs(fn, s, (wcslen(s) << 1) + 2);
if (!mt32_check("mt32emu_add_rom_file", mt32emu_add_rom_file(context, fn), MT32EMU_RC_ADDED_PCM_ROM)) return 0;
if (!mt32_check("mt32emu_open_synth", mt32emu_open_synth(context), MT32EMU_RC_OK)) return 0;
event = thread_create_event();
thread_h = thread_create(mt32_thread, 0);
samplerate = mt32emu_get_actual_stereo_output_samplerate(context);
buf_size = samplerate/RENDER_RATE*2;
if (sound_is_float)
{
buffer = malloc(buf_size * sizeof(float));
buffer_int16 = NULL;
}
else
{
buffer = NULL;
buffer_int16 = malloc(buf_size * sizeof(int16_t));
}
mt32emu_set_output_gain(context, device_get_config_int("output_gain")/100.0f);
mt32emu_set_reverb_enabled(context, device_get_config_int("reverb"));
mt32emu_set_reverb_output_gain(context, device_get_config_int("reverb_output_gain")/100.0f);
mt32emu_set_reversed_stereo_enabled(context, device_get_config_int("reversed_stereo"));
pclog("mt32 output gain: %f\n", mt32emu_get_output_gain(context));
pclog("mt32 reverb output gain: %f\n", mt32emu_get_reverb_output_gain(context));
pclog("mt32 reverb: %d\n", mt32emu_is_reverb_enabled(context));
pclog("mt32 reversed stereo: %d\n", mt32emu_is_reversed_stereo_enabled(context));
al_set_midi(samplerate, buf_size);
pclog("mt32 (Munt %s) initialized, samplerate %d, buf_size %d\n", mt32emu_get_library_version_string(), samplerate, buf_size);
midi_device_t* dev = malloc(sizeof(midi_device_t));
memset(dev, 0, sizeof(midi_device_t));
dev->play_msg = mt32_msg;
dev->play_sysex = mt32_sysex;
dev->poll = mt32_poll;
midi_init(dev);
return dev;
}
void mt32_close(void* p)
{
if (!p) return;
if (thread_h)
thread_kill(thread_h);
if (event)
thread_destroy_event(event);
event = NULL;
thread_h = NULL;
if (context)
{
mt32emu_close_synth(context);
mt32emu_free_context(context);
}
context = NULL;
if (buffer)
free(buffer);
buffer = NULL;
if (buffer_int16)
free(buffer_int16);
buffer_int16 = NULL;
midi_close();
free((midi_device_t*)p);
pclog("mt32 closed\n");
}
static device_config_t mt32_config[] =
{
{
.name = "output_gain",
.description = "Output Gain",
.type = CONFIG_SELECTION,
.selection =
{
{
.description = "100%",
.value = 100
},
{
.description = "75%",
.value = 75
},
{
.description = "50%",
.value = 50
},
{
.description = "25%",
.value = 25
},
{
.description = "0%",
.value = 0
},
{
.description = ""
}
},
.default_int = 100
},
{
.name = "reverb",
.description = "Reverb",
.type = CONFIG_BINARY,
.default_int = 1
},
{
.name = "reverb_output_gain",
.description = "Reverb Output Gain",
.type = CONFIG_SELECTION,
.selection =
{
{
.description = "100%",
.value = 100
},
{
.description = "75%",
.value = 75
},
{
.description = "50%",
.value = 50
},
{
.description = "25%",
.value = 25
},
{
.description = "0%",
.value = 0
},
{
.description = ""
}
},
.default_int = 100
},
{
.name = "reversed_stereo",
.description = "Reversed stereo",
.type = CONFIG_BINARY,
.default_int = 0
},
{
.type = -1
}
};
device_t mt32_device =
{
"Roland MT-32 Emulation",
0,
mt32_init,
mt32_close,
mt32_available,
NULL,
NULL,
NULL,
mt32_config
};

1
src/SOUND/midi_mt32.h Normal file
View File

@@ -0,0 +1 @@
extern device_t mt32_device;

62
src/SOUND/midi_system.c Normal file
View File

@@ -0,0 +1,62 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "../device.h"
#include "../WIN/plat_midi.h"
#include "midi_system.h"
#include "midi.h"
void* system_midi_init()
{
midi_device_t* dev = malloc(sizeof(midi_device_t));
memset(dev, 0, sizeof(midi_device_t));
dev->play_msg = plat_midi_play_msg;
dev->play_sysex = plat_midi_play_sysex;
dev->write = plat_midi_write;
plat_midi_init();
midi_init(dev);
return dev;
}
void system_midi_close(void* p)
{
plat_midi_close();
midi_close();
}
int system_midi_available()
{
return plat_midi_get_num_devs();
}
static device_config_t system_midi_config[] =
{
{
.name = "midi",
.description = "MIDI out device",
.type = CONFIG_MIDI,
.default_int = 0
},
{
.type = -1
}
};
device_t system_midi_device =
{
SYSTEM_MIDI_NAME,
0,
system_midi_init,
system_midi_close,
system_midi_available,
NULL,
NULL,
NULL,
system_midi_config
};

1
src/SOUND/midi_system.h Normal file
View File

@@ -0,0 +1 @@
extern device_t system_midi_device;

424
src/SOUND/munt/Analog.cpp Normal file
View File

@@ -0,0 +1,424 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include "internals.h"
#include "Analog.h"
#include "Synth.h"
namespace MT32Emu {
/* FIR approximation of the overall impulse response of the cascade composed of the sample & hold circuit and the low pass filter
* of the MT-32 first generation.
* The coefficients below are found by windowing the inverse DFT of the 1024 pin frequency response converted to the minimum phase.
* The frequency response of the LPF is computed directly, the effect of the S&H is approximated by multiplying the LPF frequency
* response by the corresponding sinc. Although, the LPF has DC gain of 3.2, we ignore this in the emulation and use normalised model.
* The peak gain of the normalised cascade appears about 1.7 near 11.8 kHz. Relative error doesn't exceed 1% for the frequencies
* below 12.5 kHz. In the higher frequency range, the relative error is below 8%. Peak error value is at 16 kHz.
*/
static const FloatSample COARSE_LPF_FLOAT_TAPS_MT32[] = {
1.272473681f, -0.220267785f, -0.158039905f, 0.179603785f, -0.111484097f, 0.054137498f, -0.023518029f, 0.010997169f, -0.006935698f
};
// Similar approximation for new MT-32 and CM-32L/LAPC-I LPF. As the voltage controlled amplifier was introduced, LPF has unity DC gain.
// The peak gain value shifted towards higher frequencies and a bit higher about 1.83 near 13 kHz.
static const FloatSample COARSE_LPF_FLOAT_TAPS_CM32L[] = {
1.340615635f, -0.403331694f, 0.036005517f, 0.066156844f, -0.069672532f, 0.049563806f, -0.031113416f, 0.019169774f, -0.012421368f
};
static const unsigned int COARSE_LPF_INT_FRACTION_BITS = 14;
// Integer versions of the FIRs above multiplied by (1 << 14) and rounded.
static const IntSampleEx COARSE_LPF_INT_TAPS_MT32[] = {
20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114
};
static const IntSampleEx COARSE_LPF_INT_TAPS_CM32L[] = {
21965, -6608, 590, 1084, -1142, 812, -510, 314, -204
};
/* Combined FIR that both approximates the impulse response of the analogue circuits of sample & hold and the low pass filter
* in the audible frequency range (below 20 kHz) and attenuates unwanted mirror spectra above 28 kHz as well. It is a polyphase
* filter intended for resampling the signal to 48 kHz yet for applying high frequency boost.
* As with the filter above, the analogue LPF frequency response is obtained for 1536 pin grid for range up to 96 kHz and multiplied
* by the corresponding sinc. The result is further squared, windowed and passed to generalised Parks-McClellan routine as a desired response.
* Finally, the minimum phase factor is found that's essentially the coefficients below.
* Relative error in the audible frequency range doesn't exceed 0.0006%, attenuation in the stopband is better than 100 dB.
* This level of performance makes it nearly bit-accurate for standard 16-bit sample resolution.
*/
// FIR version for MT-32 first generation.
static const FloatSample ACCURATE_LPF_TAPS_MT32[] = {
0.003429281f, 0.025929869f, 0.096587777f, 0.228884848f, 0.372413431f, 0.412386503f, 0.263980018f,
-0.014504962f, -0.237394528f, -0.257043496f, -0.103436603f, 0.063996095f, 0.124562333f, 0.083703206f,
0.013921662f, -0.033475018f, -0.046239712f, -0.029310921f, 0.00126585f, 0.021060961f, 0.017925605f,
0.003559874f, -0.005105248f, -0.005647917f, -0.004157918f, -0.002065664f, 0.00158747f, 0.003762585f,
0.001867137f, -0.001090028f, -0.001433979f, -0.00022367f, 4.34308E-05f, -0.000247827f, 0.000157087f,
0.000605823f, 0.000197317f, -0.000370511f, -0.000261202f, 9.96069E-05f, 9.85073E-05f, -5.28754E-05f,
-1.00912E-05f, 7.69943E-05f, 2.03162E-05f, -5.67967E-05f, -3.30637E-05f, 1.61958E-05f, 1.73041E-05f
};
// FIR version for new MT-32 and CM-32L/LAPC-I.
static const FloatSample ACCURATE_LPF_TAPS_CM32L[] = {
0.003917452f, 0.030693861f, 0.116424199f, 0.275101674f, 0.43217361f, 0.431247894f, 0.183255659f,
-0.174955671f, -0.354240244f, -0.212401714f, 0.072259178f, 0.204655344f, 0.108336211f, -0.039099027f,
-0.075138174f, -0.026261906f, 0.00582663f, 0.003052193f, 0.00613657f, 0.017017951f, 0.008732535f,
-0.011027427f, -0.012933664f, 0.001158097f, 0.006765958f, 0.00046778f, -0.002191106f, 0.001561017f,
0.001842871f, -0.001996876f, -0.002315836f, 0.000980965f, 0.001817454f, -0.000243272f, -0.000972848f,
0.000149941f, 0.000498886f, -0.000204436f, -0.000347415f, 0.000142386f, 0.000249137f, -4.32946E-05f,
-0.000131231f, 3.88575E-07f, 4.48813E-05f, -1.31906E-06f, -1.03499E-05f, 7.71971E-06f, 2.86721E-06f
};
// According to the CM-64 PCB schematic, there is a difference in the values of the LPF entrance resistors for the reverb and non-reverb channels.
// This effectively results in non-unity LPF DC gain for the reverb channel of 0.68 while the LPF has unity DC gain for the LA32 output channels.
// In emulation, the reverb output gain is multiplied by this factor to compensate for the LPF gain difference.
static const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f;
static const unsigned int OUTPUT_GAIN_FRACTION_BITS = 8;
static const float OUTPUT_GAIN_MULTIPLIER = float(1 << OUTPUT_GAIN_FRACTION_BITS);
static const unsigned int COARSE_LPF_DELAY_LINE_LENGTH = 8; // Must be a power of 2
static const unsigned int ACCURATE_LPF_DELAY_LINE_LENGTH = 16; // Must be a power of 2
static const unsigned int ACCURATE_LPF_NUMBER_OF_PHASES = 3; // Upsampling factor
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_REGULAR = 2; // Downsampling factor
static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No downsampling
static const Bit32u ACCURATE_LPF_DELTAS_REGULAR[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 2, 1 } };
static const Bit32u ACCURATE_LPF_DELTAS_OVERSAMPLED[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 0, 1 } };
template <class SampleEx>
class AbstractLowPassFilter {
public:
static AbstractLowPassFilter<SampleEx> &createLowPassFilter(const AnalogOutputMode mode, const bool oldMT32AnalogLPF);
virtual ~AbstractLowPassFilter() {}
virtual SampleEx process(const SampleEx sample) = 0;
virtual bool hasNextSample() const {
return false;
}
virtual unsigned int getOutputSampleRate() const {
return SAMPLE_RATE;
}
virtual unsigned int estimateInSampleCount(const unsigned int outSamples) const {
return outSamples;
}
virtual void addPositionIncrement(const unsigned int) {}
};
template <class SampleEx>
class NullLowPassFilter : public AbstractLowPassFilter<SampleEx> {
public:
SampleEx process(const SampleEx sample) {
return sample;
}
};
template <class SampleEx>
class CoarseLowPassFilter : public AbstractLowPassFilter<SampleEx> {
private:
const SampleEx * const lpfTaps;
SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH];
unsigned int ringBufferPosition;
public:
static inline const SampleEx *getLPFTaps(const bool oldMT32AnalogLPF);
static inline SampleEx normaliseSample(const SampleEx sample);
explicit CoarseLowPassFilter(const bool oldMT32AnalogLPF) :
lpfTaps(getLPFTaps(oldMT32AnalogLPF)),
ringBufferPosition(0)
{
Synth::muteSampleBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH);
}
SampleEx process(const SampleEx inSample) {
static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1;
SampleEx sample = lpfTaps[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition];
ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample);
for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) {
sample += lpfTaps[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK];
}
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
return normaliseSample(sample);
}
};
class AccurateLowPassFilter : public AbstractLowPassFilter<IntSampleEx>, public AbstractLowPassFilter<FloatSample> {
private:
const FloatSample * const LPF_TAPS;
const Bit32u (* const deltas)[ACCURATE_LPF_NUMBER_OF_PHASES];
const unsigned int phaseIncrement;
const unsigned int outputSampleRate;
FloatSample ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH];
unsigned int ringBufferPosition;
unsigned int phase;
public:
AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample);
FloatSample process(const FloatSample sample);
IntSampleEx process(const IntSampleEx sample);
bool hasNextSample() const;
unsigned int getOutputSampleRate() const;
unsigned int estimateInSampleCount(const unsigned int outSamples) const;
void addPositionIncrement(const unsigned int positionIncrement);
};
static inline IntSampleEx normaliseSample(const IntSampleEx sample) {
return sample >> OUTPUT_GAIN_FRACTION_BITS;
}
static inline FloatSample normaliseSample(const FloatSample sample) {
return sample;
}
static inline float getActualReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode) {
return mt32ReverbCompatibilityMode ? reverbGain : reverbGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR;
}
static inline IntSampleEx getIntOutputGain(const float outputGain) {
return IntSampleEx(((OUTPUT_GAIN_MULTIPLIER < outputGain) ? OUTPUT_GAIN_MULTIPLIER : outputGain) * OUTPUT_GAIN_MULTIPLIER);
}
template <class SampleEx>
class AnalogImpl : public Analog {
public:
AbstractLowPassFilter<SampleEx> &leftChannelLPF;
AbstractLowPassFilter<SampleEx> &rightChannelLPF;
SampleEx synthGain;
SampleEx reverbGain;
AnalogImpl(const AnalogOutputMode mode, const bool oldMT32AnalogLPF) :
leftChannelLPF(AbstractLowPassFilter<SampleEx>::createLowPassFilter(mode, oldMT32AnalogLPF)),
rightChannelLPF(AbstractLowPassFilter<SampleEx>::createLowPassFilter(mode, oldMT32AnalogLPF)),
synthGain(0),
reverbGain(0)
{}
~AnalogImpl() {
delete &leftChannelLPF;
delete &rightChannelLPF;
}
unsigned int getOutputSampleRate() const {
return leftChannelLPF.getOutputSampleRate();
}
Bit32u getDACStreamsLength(const Bit32u outputLength) const {
return leftChannelLPF.estimateInSampleCount(outputLength);
}
void setSynthOutputGain(const float synthGain);
void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode);
bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength);
bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength);
template <class Sample>
void produceOutput(Sample *outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) {
if (outStream == NULL) {
leftChannelLPF.addPositionIncrement(outLength);
rightChannelLPF.addPositionIncrement(outLength);
return;
}
while (0 < (outLength--)) {
SampleEx outSampleL;
SampleEx outSampleR;
if (leftChannelLPF.hasNextSample()) {
outSampleL = leftChannelLPF.process(0);
outSampleR = rightChannelLPF.process(0);
} else {
SampleEx inSampleL = (SampleEx(*(nonReverbLeft++)) + SampleEx(*(reverbDryLeft++))) * synthGain + SampleEx(*(reverbWetLeft++)) * reverbGain;
SampleEx inSampleR = (SampleEx(*(nonReverbRight++)) + SampleEx(*(reverbDryRight++))) * synthGain + SampleEx(*(reverbWetRight++)) * reverbGain;
outSampleL = leftChannelLPF.process(normaliseSample(inSampleL));
outSampleR = rightChannelLPF.process(normaliseSample(inSampleR));
}
*(outStream++) = Synth::clipSampleEx(outSampleL);
*(outStream++) = Synth::clipSampleEx(outSampleR);
}
}
};
Analog *Analog::createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType) {
switch (rendererType)
{
case RendererType_BIT16S:
return new AnalogImpl<IntSampleEx>(mode, oldMT32AnalogLPF);
case RendererType_FLOAT:
return new AnalogImpl<FloatSample>(mode, oldMT32AnalogLPF);
}
return NULL;
}
template<>
bool AnalogImpl<IntSampleEx>::process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength) {
produceOutput(outStream, nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, outLength);
return true;
}
template<>
bool AnalogImpl<FloatSample>::process(IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, const IntSample *, Bit32u) {
return false;
}
template<>
bool AnalogImpl<IntSampleEx>::process(FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, const FloatSample *, Bit32u) {
return false;
}
template<>
bool AnalogImpl<FloatSample>::process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength) {
produceOutput(outStream, nonReverbLeft, nonReverbRight, reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, outLength);
return true;
}
template<>
void AnalogImpl<IntSampleEx>::setSynthOutputGain(const float useSynthGain) {
synthGain = getIntOutputGain(useSynthGain);
}
template<>
void AnalogImpl<IntSampleEx>::setReverbOutputGain(const float useReverbGain, const bool mt32ReverbCompatibilityMode) {
reverbGain = getIntOutputGain(getActualReverbOutputGain(useReverbGain, mt32ReverbCompatibilityMode));
}
template<>
void AnalogImpl<FloatSample>::setSynthOutputGain(const float useSynthGain) {
synthGain = useSynthGain;
}
template<>
void AnalogImpl<FloatSample>::setReverbOutputGain(const float useReverbGain, const bool mt32ReverbCompatibilityMode) {
reverbGain = getActualReverbOutputGain(useReverbGain, mt32ReverbCompatibilityMode);
}
template<>
AbstractLowPassFilter<IntSampleEx> &AbstractLowPassFilter<IntSampleEx>::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) {
switch (mode) {
case AnalogOutputMode_COARSE:
return *new CoarseLowPassFilter<IntSampleEx>(oldMT32AnalogLPF);
case AnalogOutputMode_ACCURATE:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, false);
case AnalogOutputMode_OVERSAMPLED:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, true);
default:
return *new NullLowPassFilter<IntSampleEx>;
}
}
template<>
AbstractLowPassFilter<FloatSample> &AbstractLowPassFilter<FloatSample>::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) {
switch (mode) {
case AnalogOutputMode_COARSE:
return *new CoarseLowPassFilter<FloatSample>(oldMT32AnalogLPF);
case AnalogOutputMode_ACCURATE:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, false);
case AnalogOutputMode_OVERSAMPLED:
return *new AccurateLowPassFilter(oldMT32AnalogLPF, true);
default:
return *new NullLowPassFilter<FloatSample>;
}
}
template<>
const IntSampleEx *CoarseLowPassFilter<IntSampleEx>::getLPFTaps(const bool oldMT32AnalogLPF) {
return oldMT32AnalogLPF ? COARSE_LPF_INT_TAPS_MT32 : COARSE_LPF_INT_TAPS_CM32L;
}
template<>
const FloatSample *CoarseLowPassFilter<FloatSample>::getLPFTaps(const bool oldMT32AnalogLPF) {
return oldMT32AnalogLPF ? COARSE_LPF_FLOAT_TAPS_MT32 : COARSE_LPF_FLOAT_TAPS_CM32L;
}
template<>
IntSampleEx CoarseLowPassFilter<IntSampleEx>::normaliseSample(const IntSampleEx sample) {
return sample >> COARSE_LPF_INT_FRACTION_BITS;
}
template<>
FloatSample CoarseLowPassFilter<FloatSample>::normaliseSample(const FloatSample sample) {
return sample;
}
AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample) :
LPF_TAPS(oldMT32AnalogLPF ? ACCURATE_LPF_TAPS_MT32 : ACCURATE_LPF_TAPS_CM32L),
deltas(oversample ? ACCURATE_LPF_DELTAS_OVERSAMPLED : ACCURATE_LPF_DELTAS_REGULAR),
phaseIncrement(oversample ? ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED : ACCURATE_LPF_PHASE_INCREMENT_REGULAR),
outputSampleRate(SAMPLE_RATE * ACCURATE_LPF_NUMBER_OF_PHASES / phaseIncrement),
ringBufferPosition(0),
phase(0)
{
Synth::muteSampleBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH);
}
FloatSample AccurateLowPassFilter::process(const FloatSample inSample) {
static const unsigned int DELAY_LINE_MASK = ACCURATE_LPF_DELAY_LINE_LENGTH - 1;
FloatSample sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f;
if (!hasNextSample()) {
ringBuffer[ringBufferPosition] = inSample;
}
for (unsigned int tapIx = phase, delaySampleIx = 0; delaySampleIx < ACCURATE_LPF_DELAY_LINE_LENGTH; delaySampleIx++, tapIx += ACCURATE_LPF_NUMBER_OF_PHASES) {
sample += LPF_TAPS[tapIx] * ringBuffer[(delaySampleIx + ringBufferPosition) & DELAY_LINE_MASK];
}
phase += phaseIncrement;
if (ACCURATE_LPF_NUMBER_OF_PHASES <= phase) {
phase -= ACCURATE_LPF_NUMBER_OF_PHASES;
ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK;
}
return ACCURATE_LPF_NUMBER_OF_PHASES * sample;
}
IntSampleEx AccurateLowPassFilter::process(const IntSampleEx sample) {
return IntSampleEx(process(FloatSample(sample)));
}
bool AccurateLowPassFilter::hasNextSample() const {
return phaseIncrement <= phase;
}
unsigned int AccurateLowPassFilter::getOutputSampleRate() const {
return outputSampleRate;
}
unsigned int AccurateLowPassFilter::estimateInSampleCount(const unsigned int outSamples) const {
Bit32u cycleCount = outSamples / ACCURATE_LPF_NUMBER_OF_PHASES;
Bit32u remainder = outSamples - cycleCount * ACCURATE_LPF_NUMBER_OF_PHASES;
return cycleCount * phaseIncrement + deltas[remainder][phase];
}
void AccurateLowPassFilter::addPositionIncrement(const unsigned int positionIncrement) {
phase = (phase + positionIncrement * phaseIncrement) % ACCURATE_LPF_NUMBER_OF_PHASES;
}
} // namespace MT32Emu

53
src/SOUND/munt/Analog.h Normal file
View File

@@ -0,0 +1,53 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_ANALOG_H
#define MT32EMU_ANALOG_H
#include "globals.h"
#include "internals.h"
#include "Enumerations.h"
#include "Types.h"
namespace MT32Emu {
/* Analog class is dedicated to perform fair emulation of analogue circuitry of hardware units that is responsible
* for processing output signal after the DAC. It appears that the analogue circuit labeled "LPF" on the schematic
* also applies audible changes to the signal spectra. There is a significant boost of higher frequencies observed
* aside from quite poor attenuation of the mirror spectra above 16 kHz which is due to a relatively low filter order.
*
* As the final mixing of multiplexed output signal is performed after the DAC, this function is migrated here from Synth.
* Saying precisely, mixing is performed within the LPF as the entrance resistors are actually components of a LPF
* designed using the multiple feedback topology. Nevertheless, the schematic separates them.
*/
class Analog {
public:
static Analog *createAnalog(const AnalogOutputMode mode, const bool oldMT32AnalogLPF, const RendererType rendererType);
virtual ~Analog() {};
virtual unsigned int getOutputSampleRate() const = 0;
virtual Bit32u getDACStreamsLength(const Bit32u outputLength) const = 0;
virtual void setSynthOutputGain(const float synthGain) = 0;
virtual void setReverbOutputGain(const float reverbGain, const bool mt32ReverbCompatibilityMode) = 0;
virtual bool process(IntSample *outStream, const IntSample *nonReverbLeft, const IntSample *nonReverbRight, const IntSample *reverbDryLeft, const IntSample *reverbDryRight, const IntSample *reverbWetLeft, const IntSample *reverbWetRight, Bit32u outLength) = 0;
virtual bool process(FloatSample *outStream, const FloatSample *nonReverbLeft, const FloatSample *nonReverbRight, const FloatSample *reverbDryLeft, const FloatSample *reverbDryRight, const FloatSample *reverbWetLeft, const FloatSample *reverbWetRight, Bit32u outLength) = 0;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_ANALOG_H

View File

@@ -0,0 +1,662 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "internals.h"
#include "BReverbModel.h"
#include "Synth.h"
// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that
// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF)
// and followed by three parallel comb filters
namespace MT32Emu {
// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay,
// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data.
// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level,
// so we can simply increase the input buffer size.
static const Bit32u PROCESS_DELAY = 1;
static const Bit32u MODE_3_ADDITIONAL_DELAY = 1;
static const Bit32u MODE_3_FEEDBACK_DELAY = 1;
// Avoid denormals degrading performance, using biased input
static const FloatSample BIAS = 1e-20f;
struct BReverbSettings {
const Bit32u numberOfAllpasses;
const Bit32u * const allpassSizes;
const Bit32u numberOfCombs;
const Bit32u * const combSizes;
const Bit32u * const outLPositions;
const Bit32u * const outRPositions;
const Bit8u * const filterFactors;
const Bit8u * const feedbackFactors;
const Bit8u * const dryAmps;
const Bit8u * const wetLevels;
const Bit8u lpfAmp;
};
// Default reverb settings for "new" reverb model implemented in CM-32L / LAPC-I.
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode) {
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb.
static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632};
static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960};
static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145};
static const Bit8u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60};
static const Bit8u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit8u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0};
static const Bit8u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit8u MODE_0_LPF_AMP = 0x60;
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
static const Bit8u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60};
static const Bit8u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit8u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0};
static const Bit8u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit8u MODE_1_LPF_AMP = 0x60;
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
static const Bit8u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20};
static const Bit8u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0,
0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0};
static const Bit8u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0};
static const Bit8u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0};
static const Bit8u MODE_2_LPF_AMP = 0x80;
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
static const Bit8u MODE_3_COMB_FACTOR[] = {0x68};
static const Bit8u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
static const Bit8u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50};
static const Bit8u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
return *REVERB_SETTINGS[mode];
}
// Default reverb settings for "old" reverb model implemented in MT-32.
// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog).
static const BReverbSettings &getMT32Settings(const ReverbMode mode) {
static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78};
static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
static const Bit32u MODE_0_COMBS[] = {575 + PROCESS_DELAY, 2040, 2752, 3629};
static const Bit32u MODE_0_OUTL[] = {2040, 687, 1814};
static const Bit32u MODE_0_OUTR[] = {1019, 2072, 1};
static const Bit8u MODE_0_COMB_FACTOR[] = {0xB0, 0x60, 0x60, 0x60};
static const Bit8u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit8u MODE_0_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
static const Bit8u MODE_0_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
static const Bit8u MODE_0_LPF_AMP = 0x80;
static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176};
static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519};
static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518};
static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274};
static const Bit8u MODE_1_COMB_FACTOR[] = {0x90, 0x60, 0x60, 0x60};
static const Bit8u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit8u MODE_1_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
static const Bit8u MODE_1_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
static const Bit8u MODE_1_LPF_AMP = 0x80;
static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3;
static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157};
static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as above in the new model implementation
static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539};
static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769};
static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1};
static const Bit8u MODE_2_COMB_FACTOR[] = {0, 0x60, 0x60, 0x60};
static const Bit8u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98,
0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98};
static const Bit8u MODE_2_DRY_AMP[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80};
static const Bit8u MODE_2_WET_AMP[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x70, 0xA0, 0xE0};
static const Bit8u MODE_2_LPF_AMP = 0x80;
static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0;
static const Bit32u MODE_3_NUMBER_OF_COMBS = 1;
static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY};
static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000};
static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000};
static const Bit8u MODE_3_COMB_FACTOR[] = {0x68};
static const Bit8u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60};
static const Bit8u MODE_3_DRY_AMP[] = {0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x10, 0x20, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10};
static const Bit8u MODE_3_WET_AMP[] = {0x08, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8};
static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP};
static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP};
static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP};
static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0};
static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS};
return *REVERB_SETTINGS[mode];
}
static inline IntSample weirdMul(IntSample sample, Bit8u addMask, Bit8u carryMask) {
#if MT32EMU_BOSS_REVERB_PRECISE_MODE
// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines).
Bit8u mask = 0x80;
IntSampleEx res = 0;
for (int i = 0; i < 8; i++) {
IntSampleEx carry = (sample < 0) && (mask & carryMask) > 0 ? sample & 1 : 0;
sample >>= 1;
res += (mask & addMask) > 0 ? sample + carry : 0;
mask >>= 1;
}
return IntSample(res);
#else
(void)carryMask;
return IntSample((IntSampleEx(sample) * addMask) >> 8);
#endif
}
static inline FloatSample weirdMul(FloatSample sample, Bit8u addMask, Bit8u carryMask) {
(void)carryMask;
return sample * addMask / 256.0f;
}
static inline IntSample halveSample(IntSample sample) {
return sample >> 1;
}
static inline FloatSample halveSample(FloatSample sample) {
return 0.5f * sample;
}
static inline IntSample quarterSample(IntSample sample) {
#if MT32EMU_BOSS_REVERB_PRECISE_MODE
return (sample >> 1) / 2;
#else
return sample >> 2;
#endif
}
static inline FloatSample quarterSample(FloatSample sample) {
return 0.25f * sample;
}
static inline IntSample addDCBias(IntSample sample) {
return sample;
}
static inline FloatSample addDCBias(FloatSample sample) {
return sample + BIAS;
}
static inline IntSample addAllpassNoise(IntSample sample) {
#if MT32EMU_BOSS_REVERB_PRECISE_MODE
// This introduces reverb noise which actually makes output from the real Boss chip nondeterministic
return sample - 1;
#else
return sample;
#endif
}
static inline FloatSample addAllpassNoise(FloatSample sample) {
return sample;
}
/* NOTE:
* Thanks to Mok for discovering, the adder in BOSS reverb chip is found to perform addition with saturation to avoid integer overflow.
* Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below.
* So, despite this isn't actually accurate, we only add the check here for performance reasons.
*/
static inline IntSample mixCombs(IntSample out1, IntSample out2, IntSample out3) {
#if MT32EMU_BOSS_REVERB_PRECISE_MODE
return Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(IntSampleEx(out1) + (IntSampleEx(out1) >> 1)) + IntSampleEx(out2)) + (IntSampleEx(out2) >> 1)) + IntSampleEx(out3));
#else
return Synth::clipSampleEx(IntSampleEx(out1) + (IntSampleEx(out1) >> 1) + IntSampleEx(out2) + (IntSampleEx(out2) >> 1) + IntSampleEx(out3));
#endif
}
static inline FloatSample mixCombs(FloatSample out1, FloatSample out2, FloatSample out3) {
return 1.5f * (out1 + out2) + out3;
}
template <class Sample>
class RingBuffer {
static inline Sample sampleValueThreshold();
protected:
Sample *buffer;
const Bit32u size;
Bit32u index;
public:
RingBuffer(const Bit32u newsize) : size(newsize), index(0) {
buffer = new Sample[size];
}
virtual ~RingBuffer() {
delete[] buffer;
buffer = NULL;
}
Sample next() {
if (++index >= size) {
index = 0;
}
return buffer[index];
}
bool isEmpty() const {
if (buffer == NULL) return true;
Sample *buf = buffer;
for (Bit32u i = 0; i < size; i++) {
if (*buf < -sampleValueThreshold() || *buf > sampleValueThreshold()) return false;
buf++;
}
return true;
}
void mute() {
Synth::muteSampleBuffer(buffer, size);
}
};
template<>
IntSample RingBuffer<IntSample>::sampleValueThreshold() {
return 8;
}
template<>
FloatSample RingBuffer<FloatSample>::sampleValueThreshold() {
return 0.001f;
}
template <class Sample>
class AllpassFilter : public RingBuffer<Sample> {
public:
AllpassFilter(const Bit32u useSize) : RingBuffer<Sample>(useSize) {}
// This model corresponds to the allpass filter implementation of the real CM-32L device
// found from sample analysis
Sample process(const Sample in) {
const Sample bufferOut = this->next();
// store input - feedback / 2
this->buffer[this->index] = in - halveSample(bufferOut);
// return buffer output + feedforward / 2
return bufferOut + halveSample(this->buffer[this->index]);
}
};
template <class Sample>
class CombFilter : public RingBuffer<Sample> {
protected:
const Bit8u filterFactor;
Bit8u feedbackFactor;
public:
CombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : RingBuffer<Sample>(useSize), filterFactor(useFilterFactor) {}
// This model corresponds to the comb filter implementation of the real CM-32L device
void process(const Sample in) {
// the previously stored value
const Sample last = this->buffer[this->index];
// prepare input + feedback
const Sample filterIn = in + weirdMul(this->next(), feedbackFactor, 0xF0);
// store input + feedback processed by a low-pass filter
this->buffer[this->index] = weirdMul(last, filterFactor, 0xC0) - filterIn;
}
Sample getOutputAt(const Bit32u outIndex) const {
return this->buffer[(this->size + this->index - outIndex) % this->size];
}
void setFeedbackFactor(const Bit8u useFeedbackFactor) {
feedbackFactor = useFeedbackFactor;
}
};
template <class Sample>
class DelayWithLowPassFilter : public CombFilter<Sample> {
Bit8u amp;
public:
DelayWithLowPassFilter(const Bit32u useSize, const Bit8u useFilterFactor, const Bit8u useAmp)
: CombFilter<Sample>(useSize, useFilterFactor), amp(useAmp) {}
void process(const Sample in) {
// the previously stored value
const Sample last = this->buffer[this->index];
// move to the next index
this->next();
// low-pass filter process
Sample lpfOut = weirdMul(last, this->filterFactor, 0xFF) + in;
// store lpfOut multiplied by LPF amp factor
this->buffer[this->index] = weirdMul(lpfOut, amp, 0xFF);
}
};
template <class Sample>
class TapDelayCombFilter : public CombFilter<Sample> {
Bit32u outL;
Bit32u outR;
public:
TapDelayCombFilter(const Bit32u useSize, const Bit8u useFilterFactor) : CombFilter<Sample>(useSize, useFilterFactor) {}
void process(const Sample in) {
// the previously stored value
const Sample last = this->buffer[this->index];
// move to the next index
this->next();
// prepare input + feedback
// Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output
const Sample filterIn = in + weirdMul(this->getOutputAt(outR + MODE_3_FEEDBACK_DELAY), this->feedbackFactor, 0xF0);
// store input + feedback processed by a low-pass filter
this->buffer[this->index] = weirdMul(last, this->filterFactor, 0xF0) - filterIn;
}
Sample getLeftOutput() const {
return this->getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
}
Sample getRightOutput() const {
return this->getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY);
}
void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) {
outL = useOutL;
outR = useOutR;
}
};
template <class Sample>
class BReverbModelImpl : public BReverbModel {
public:
AllpassFilter<Sample> **allpasses;
CombFilter<Sample> **combs;
const BReverbSettings &currentSettings;
const bool tapDelayMode;
Bit8u dryAmp;
Bit8u wetLevel;
BReverbModelImpl(const ReverbMode mode, const bool mt32CompatibleModel) :
allpasses(NULL), combs(NULL),
currentSettings(mt32CompatibleModel ? getMT32Settings(mode) : getCM32L_LAPCSettings(mode)),
tapDelayMode(mode == REVERB_MODE_TAP_DELAY)
{}
~BReverbModelImpl() {
close();
}
bool isOpen() const {
return combs != NULL;
}
void open() {
if (isOpen()) return;
if (currentSettings.numberOfAllpasses > 0) {
allpasses = new AllpassFilter<Sample>*[currentSettings.numberOfAllpasses];
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
allpasses[i] = new AllpassFilter<Sample>(currentSettings.allpassSizes[i]);
}
}
combs = new CombFilter<Sample>*[currentSettings.numberOfCombs];
if (tapDelayMode) {
*combs = new TapDelayCombFilter<Sample>(*currentSettings.combSizes, *currentSettings.filterFactors);
} else {
combs[0] = new DelayWithLowPassFilter<Sample>(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp);
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
combs[i] = new CombFilter<Sample>(currentSettings.combSizes[i], currentSettings.filterFactors[i]);
}
}
mute();
}
void close() {
if (allpasses != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
if (allpasses[i] != NULL) {
delete allpasses[i];
allpasses[i] = NULL;
}
}
delete[] allpasses;
allpasses = NULL;
}
if (combs != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
if (combs[i] != NULL) {
delete combs[i];
combs[i] = NULL;
}
}
delete[] combs;
combs = NULL;
}
}
void mute() {
if (allpasses != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
allpasses[i]->mute();
}
}
if (combs != NULL) {
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
combs[i]->mute();
}
}
}
void setParameters(Bit8u time, Bit8u level) {
if (!isOpen()) return;
level &= 7;
time &= 7;
if (tapDelayMode) {
TapDelayCombFilter<Sample> *comb = static_cast<TapDelayCombFilter<Sample> *> (*combs);
comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]);
comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]);
} else {
for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) {
combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]);
}
}
if (time == 0 && level == 0) {
dryAmp = wetLevel = 0;
} else {
if (tapDelayMode && ((time == 0) || (time == 1 && level == 1))) {
// Looks like MT-32 implementation has some minor quirks in this mode:
// for odd level values, the output level changes sometimes depending on the time value which doesn't seem right.
dryAmp = currentSettings.dryAmps[level + 8];
} else {
dryAmp = currentSettings.dryAmps[level];
}
wetLevel = currentSettings.wetLevels[level];
}
}
bool isActive() const {
if (!isOpen()) return false;
for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) {
if (!allpasses[i]->isEmpty()) return true;
}
for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) {
if (!combs[i]->isEmpty()) return true;
}
return false;
}
bool isMT32Compatible(const ReverbMode mode) const {
return &currentSettings == &getMT32Settings(mode);
}
template <class SampleEx>
void produceOutput(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, Bit32u numSamples) {
if (!isOpen()) {
Synth::muteSampleBuffer(outLeft, numSamples);
Synth::muteSampleBuffer(outRight, numSamples);
return;
}
while ((numSamples--) > 0) {
Sample dry;
if (tapDelayMode) {
dry = halveSample(*(inLeft++)) + halveSample(*(inRight++));
} else {
dry = quarterSample(*(inLeft++)) + quarterSample(*(inRight++));
}
// Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I
dry = weirdMul(addDCBias(dry), dryAmp, 0xFF);
if (tapDelayMode) {
TapDelayCombFilter<Sample> *comb = static_cast<TapDelayCombFilter<Sample> *>(*combs);
comb->process(dry);
if (outLeft != NULL) {
*(outLeft++) = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF);
}
if (outRight != NULL) {
*(outRight++) = weirdMul(comb->getRightOutput(), wetLevel, 0xFF);
}
} else {
DelayWithLowPassFilter<Sample> * const entranceDelay = static_cast<DelayWithLowPassFilter<Sample> *>(combs[0]);
// If the output position is equal to the comb size, get it now in order not to loose it
Sample link = entranceDelay->getOutputAt(currentSettings.combSizes[0] - 1);
// Entrance LPF. Note, comb.process() differs a bit here.
entranceDelay->process(dry);
link = allpasses[0]->process(addAllpassNoise(link));
link = allpasses[1]->process(link);
link = allpasses[2]->process(link);
// If the output position is equal to the comb size, get it now in order not to loose it
Sample outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1);
combs[1]->process(link);
combs[2]->process(link);
combs[3]->process(link);
if (outLeft != NULL) {
Sample outL2 = combs[2]->getOutputAt(currentSettings.outLPositions[1]);
Sample outL3 = combs[3]->getOutputAt(currentSettings.outLPositions[2]);
Sample outSample = mixCombs(outL1, outL2, outL3);
*(outLeft++) = weirdMul(outSample, wetLevel, 0xFF);
}
if (outRight != NULL) {
Sample outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]);
Sample outR2 = combs[2]->getOutputAt(currentSettings.outRPositions[1]);
Sample outR3 = combs[3]->getOutputAt(currentSettings.outRPositions[2]);
Sample outSample = mixCombs(outR1, outR2, outR3);
*(outRight++) = weirdMul(outSample, wetLevel, 0xFF);
}
} // if (tapDelayMode)
} // while ((numSamples--) > 0)
} // produceOutput
bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples);
bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples);
};
BReverbModel *BReverbModel::createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType) {
switch (rendererType)
{
case RendererType_BIT16S:
return new BReverbModelImpl<IntSample>(mode, mt32CompatibleModel);
case RendererType_FLOAT:
return new BReverbModelImpl<FloatSample>(mode, mt32CompatibleModel);
}
return NULL;
}
template <>
bool BReverbModelImpl<IntSample>::process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples) {
produceOutput<IntSampleEx>(inLeft, inRight, outLeft, outRight, numSamples);
return true;
}
template <>
bool BReverbModelImpl<IntSample>::process(const FloatSample *, const FloatSample *, FloatSample *, FloatSample *, Bit32u) {
return false;
}
template <>
bool BReverbModelImpl<FloatSample>::process(const IntSample *, const IntSample *, IntSample *, IntSample *, Bit32u) {
return false;
}
template <>
bool BReverbModelImpl<FloatSample>::process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples) {
produceOutput<FloatSample>(inLeft, inRight, outLeft, outRight, numSamples);
return true;
}
} // namespace MT32Emu

View File

@@ -0,0 +1,48 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_B_REVERB_MODEL_H
#define MT32EMU_B_REVERB_MODEL_H
#include "globals.h"
#include "internals.h"
#include "Enumerations.h"
#include "Types.h"
namespace MT32Emu {
class BReverbModel {
public:
static BReverbModel *createBReverbModel(const ReverbMode mode, const bool mt32CompatibleModel, const RendererType rendererType);
virtual ~BReverbModel() {};
virtual bool isOpen() const = 0;
// After construction or a close(), open() must be called at least once before any other call (with the exception of close()).
virtual void open() = 0;
// May be called multiple times without an open() in between.
virtual void close() = 0;
virtual void mute() = 0;
virtual void setParameters(Bit8u time, Bit8u level) = 0;
virtual bool isActive() const = 0;
virtual bool isMT32Compatible(const ReverbMode mode) const = 0;
virtual bool process(const IntSample *inLeft, const IntSample *inRight, IntSample *outLeft, IntSample *outRight, Bit32u numSamples) = 0;
virtual bool process(const FloatSample *inLeft, const FloatSample *inRight, FloatSample *outLeft, FloatSample *outRight, Bit32u numSamples) = 0;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_B_REVERB_MODEL_H

View File

@@ -0,0 +1,188 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Using two guards since this file may be included twice with different MT32EMU_C_ENUMERATIONS define. */
#if (!defined MT32EMU_CPP_ENUMERATIONS_H && !defined MT32EMU_C_ENUMERATIONS) || (!defined MT32EMU_C_ENUMERATIONS_H && defined MT32EMU_C_ENUMERATIONS)
#ifdef MT32EMU_C_ENUMERATIONS
#define MT32EMU_C_ENUMERATIONS_H
#define MT32EMU_DAC_INPUT_MODE_NAME mt32emu_dac_input_mode
#define MT32EMU_DAC_INPUT_MODE(ident) MT32EMU_DAC_##ident
#define MT32EMU_MIDI_DELAY_MODE_NAME mt32emu_midi_delay_mode
#define MT32EMU_MIDI_DELAY_MODE(ident) MT32EMU_MDM_##ident
#define MT32EMU_ANALOG_OUTPUT_MODE_NAME mt32emu_analog_output_mode
#define MT32EMU_ANALOG_OUTPUT_MODE(ident) MT32EMU_AOM_##ident
#define MT32EMU_PARTIAL_STATE_NAME mt32emu_partial_state
#define MT32EMU_PARTIAL_STATE(ident) MT32EMU_PS_##ident
#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME mt32emu_samplerate_conversion_quality
#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY(ident) MT32EMU_SRCQ_##ident
#define MT32EMU_RENDERER_TYPE_NAME mt32emu_renderer_type
#define MT32EMU_RENDERER_TYPE(ident) MT32EMU_RT_##ident
#else /* #ifdef MT32EMU_C_ENUMERATIONS */
#define MT32EMU_CPP_ENUMERATIONS_H
#define MT32EMU_DAC_INPUT_MODE_NAME DACInputMode
#define MT32EMU_DAC_INPUT_MODE(ident) DACInputMode_##ident
#define MT32EMU_MIDI_DELAY_MODE_NAME MIDIDelayMode
#define MT32EMU_MIDI_DELAY_MODE(ident) MIDIDelayMode_##ident
#define MT32EMU_ANALOG_OUTPUT_MODE_NAME AnalogOutputMode
#define MT32EMU_ANALOG_OUTPUT_MODE(ident) AnalogOutputMode_##ident
#define MT32EMU_PARTIAL_STATE_NAME PartialState
#define MT32EMU_PARTIAL_STATE(ident) PartialState_##ident
#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME SamplerateConversionQuality
#define MT32EMU_SAMPLERATE_CONVERSION_QUALITY(ident) SamplerateConversionQuality_##ident
#define MT32EMU_RENDERER_TYPE_NAME RendererType
#define MT32EMU_RENDERER_TYPE(ident) RendererType_##ident
namespace MT32Emu {
#endif /* #ifdef MT32EMU_C_ENUMERATIONS */
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
* some hacks in the real devices for doubling the volume.
* See also http://en.wikipedia.org/wiki/Roland_MT-32#Digital_overflow
*/
enum MT32EMU_DAC_INPUT_MODE_NAME {
/**
* Produces samples at double the volume, without tricks.
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
* Higher quality than the real devices
*/
MT32EMU_DAC_INPUT_MODE(NICE),
/**
* Produces samples that exactly match the bits output from the emulated LA32.
* Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
* Much less likely to overdrive than any other mode.
* Half the volume of any of the other modes.
* Output gain is ignored for both LA32 and reverb output.
* Perfect for developers while debugging :)
*/
MT32EMU_DAC_INPUT_MODE(PURE),
/**
* Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia).
* Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low):
* 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX
*/
MT32EMU_DAC_INPUT_MODE(GENERATION1),
/**
* Re-orders the LA32 output bits as in later generations (personally confirmed on my CM-32L - KG).
* Bit order at DAC (where each number represents the original LA32 output bit number):
* 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14
*/
MT32EMU_DAC_INPUT_MODE(GENERATION2)
};
/** Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface. */
enum MT32EMU_MIDI_DELAY_MODE_NAME {
/** Process incoming MIDI events immediately. */
MT32EMU_MIDI_DELAY_MODE(IMMEDIATE),
/**
* Delay incoming short MIDI messages as if they where transferred via a MIDI cable to a real hardware unit and immediate sysex processing.
* This ensures more accurate timing of simultaneous NoteOn messages.
*/
MT32EMU_MIDI_DELAY_MODE(DELAY_SHORT_MESSAGES_ONLY),
/** Delay all incoming MIDI events as if they where transferred via a MIDI cable to a real hardware unit.*/
MT32EMU_MIDI_DELAY_MODE(DELAY_ALL)
};
/** Methods for emulating the effects of analogue circuits of real hardware units on the output signal. */
enum MT32EMU_ANALOG_OUTPUT_MODE_NAME {
/** Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance. */
MT32EMU_ANALOG_OUTPUT_MODE(DIGITAL_ONLY),
/** Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged. */
MT32EMU_ANALOG_OUTPUT_MODE(COARSE),
/**
* Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz,
* which is passed through the LPF circuit without significant attenuation.
*/
MT32EMU_ANALOG_OUTPUT_MODE(ACCURATE),
/**
* Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz.
* This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs
* compared to a regular LPF FIR implementations.
*/
MT32EMU_ANALOG_OUTPUT_MODE(OVERSAMPLED)
};
enum MT32EMU_PARTIAL_STATE_NAME {
MT32EMU_PARTIAL_STATE(INACTIVE),
MT32EMU_PARTIAL_STATE(ATTACK),
MT32EMU_PARTIAL_STATE(SUSTAIN),
MT32EMU_PARTIAL_STATE(RELEASE)
};
enum MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME {
/** Use this only when the speed is more important than the audio quality. */
MT32EMU_SAMPLERATE_CONVERSION_QUALITY(FASTEST),
MT32EMU_SAMPLERATE_CONVERSION_QUALITY(FAST),
MT32EMU_SAMPLERATE_CONVERSION_QUALITY(GOOD),
MT32EMU_SAMPLERATE_CONVERSION_QUALITY(BEST)
};
enum MT32EMU_RENDERER_TYPE_NAME {
/** Use 16-bit signed samples in the renderer and the accurate wave generator model based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. */
MT32EMU_RENDERER_TYPE(BIT16S),
/** Use float samples in the renderer and simplified wave generator model. Maximum output quality and minimum noise. */
MT32EMU_RENDERER_TYPE(FLOAT)
};
#ifndef MT32EMU_C_ENUMERATIONS
} // namespace MT32Emu
#endif
#undef MT32EMU_DAC_INPUT_MODE_NAME
#undef MT32EMU_DAC_INPUT_MODE
#undef MT32EMU_MIDI_DELAY_MODE_NAME
#undef MT32EMU_MIDI_DELAY_MODE
#undef MT32EMU_ANALOG_OUTPUT_MODE_NAME
#undef MT32EMU_ANALOG_OUTPUT_MODE
#undef MT32EMU_PARTIAL_STATE_NAME
#undef MT32EMU_PARTIAL_STATE
#undef MT32EMU_SAMPLERATE_CONVERSION_QUALITY_NAME
#undef MT32EMU_SAMPLERATE_CONVERSION_QUALITY
#undef MT32EMU_RENDERER_TYPE_NAME
#undef MT32EMU_RENDERER_TYPE
#endif /* #if (!defined MT32EMU_CPP_ENUMERATIONS_H && !defined MT32EMU_C_ENUMERATIONS) || (!defined MT32EMU_C_ENUMERATIONS_H && defined MT32EMU_C_ENUMERATIONS) */

77
src/SOUND/munt/File.cpp Normal file
View File

@@ -0,0 +1,77 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include "internals.h"
#include "File.h"
#include "sha1/sha1.h"
namespace MT32Emu {
AbstractFile::AbstractFile() : sha1DigestCalculated(false) {
sha1Digest[0] = 0;
reserved = NULL;
}
AbstractFile::AbstractFile(const SHA1Digest &useSHA1Digest) : sha1DigestCalculated(true) {
memcpy(sha1Digest, useSHA1Digest, sizeof(SHA1Digest) - 1);
sha1Digest[sizeof(SHA1Digest) - 1] = 0; // Ensure terminator char.
reserved = NULL;
}
const File::SHA1Digest &AbstractFile::getSHA1() {
if (sha1DigestCalculated) {
return sha1Digest;
}
sha1DigestCalculated = true;
size_t size = getSize();
if (size == 0) {
return sha1Digest;
}
const Bit8u *data = getData();
if (data == NULL) {
return sha1Digest;
}
unsigned char fileDigest[20];
sha1::calc(data, int(size), fileDigest);
sha1::toHexString(fileDigest, sha1Digest);
return sha1Digest;
}
ArrayFile::ArrayFile(const Bit8u *useData, size_t useSize) : data(useData), size(useSize)
{}
ArrayFile::ArrayFile(const Bit8u *useData, size_t useSize, const SHA1Digest &useSHA1Digest) : AbstractFile(useSHA1Digest), data(useData), size(useSize)
{}
size_t ArrayFile::getSize() {
return size;
}
const Bit8u *ArrayFile::getData() {
return data;
}
} // namespace MT32Emu

73
src/SOUND/munt/File.h Normal file
View File

@@ -0,0 +1,73 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_FILE_H
#define MT32EMU_FILE_H
#include <cstddef>
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
class MT32EMU_EXPORT File {
public:
// Includes terminator char.
typedef char SHA1Digest[41];
virtual ~File() {}
virtual size_t getSize() = 0;
virtual const Bit8u *getData() = 0;
virtual const SHA1Digest &getSHA1() = 0;
virtual void close() = 0;
};
class MT32EMU_EXPORT AbstractFile : public File {
public:
const SHA1Digest &getSHA1();
protected:
AbstractFile();
AbstractFile(const SHA1Digest &sha1Digest);
private:
bool sha1DigestCalculated;
SHA1Digest sha1Digest;
// Binary compatibility helper.
void *reserved;
};
class MT32EMU_EXPORT ArrayFile : public AbstractFile {
public:
ArrayFile(const Bit8u *data, size_t size);
ArrayFile(const Bit8u *data, size_t size, const SHA1Digest &sha1Digest);
size_t getSize();
const Bit8u *getData();
void close() {}
private:
const Bit8u *data;
size_t size;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_FILE_H

View File

@@ -0,0 +1,83 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "internals.h"
#include "FileStream.h"
namespace MT32Emu {
using std::ios_base;
FileStream::FileStream() : ifsp(*new std::ifstream), data(NULL), size(0)
{}
FileStream::~FileStream() {
// destructor closes ifsp
delete &ifsp;
delete[] data;
}
size_t FileStream::getSize() {
if (size != 0) {
return size;
}
if (!ifsp.is_open()) {
return 0;
}
ifsp.seekg(0, ios_base::end);
size = size_t(ifsp.tellg());
return size;
}
const Bit8u *FileStream::getData() {
if (data != NULL) {
return data;
}
if (!ifsp.is_open()) {
return NULL;
}
if (getSize() == 0) {
return NULL;
}
Bit8u *fileData = new Bit8u[size];
if (fileData == NULL) {
return NULL;
}
ifsp.seekg(0);
ifsp.read(reinterpret_cast<char *>(fileData), std::streamsize(size));
if (size_t(ifsp.tellg()) != size) {
delete[] fileData;
return NULL;
}
data = fileData;
close();
return data;
}
bool FileStream::open(const char *filename) {
ifsp.clear();
ifsp.open(filename, ios_base::in | ios_base::binary);
return !ifsp.fail();
}
void FileStream::close() {
ifsp.close();
ifsp.clear();
}
} // namespace MT32Emu

View File

@@ -0,0 +1,46 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_FILE_STREAM_H
#define MT32EMU_FILE_STREAM_H
#include <fstream>
#include "globals.h"
#include "Types.h"
#include "File.h"
namespace MT32Emu {
class FileStream : public AbstractFile {
public:
MT32EMU_EXPORT FileStream();
MT32EMU_EXPORT ~FileStream();
MT32EMU_EXPORT size_t getSize();
MT32EMU_EXPORT const Bit8u *getData();
MT32EMU_EXPORT bool open(const char *filename);
MT32EMU_EXPORT void close();
private:
std::ifstream &ifsp;
const Bit8u *data;
size_t size;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_FILE_STREAM_H

View File

@@ -0,0 +1,360 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "internals.h"
#include "LA32FloatWaveGenerator.h"
#include "mmath.h"
#include "Tables.h"
namespace MT32Emu {
static const float MIDDLE_CUTOFF_VALUE = 128.0f;
static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f;
static const float MAX_CUTOFF_VALUE = 240.0f;
float LA32FloatWaveGenerator::getPCMSample(unsigned int position) {
if (position >= pcmWaveLength) {
if (!pcmWaveLooped) {
return 0;
}
position = position % pcmWaveLength;
}
Bit16s pcmSample = pcmWaveAddress[position];
float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f);
return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue;
}
void LA32FloatWaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
sawtoothWaveform = useSawtoothWaveform;
pulseWidth = usePulseWidth;
resonance = useResonance;
wavePos = 0.0f;
lastFreq = 0.0f;
pcmWaveAddress = NULL;
active = true;
}
void LA32FloatWaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
pcmWaveAddress = usePCMWaveAddress;
pcmWaveLength = usePCMWaveLength;
pcmWaveLooped = usePCMWaveLooped;
pcmWaveInterpolated = usePCMWaveInterpolated;
pcmPosition = 0.0f;
active = true;
}
// ampVal - Logarithmic amp of the wave generator
// pitch - Logarithmic frequency of the resulting wave
// cutoffRampVal - Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
float LA32FloatWaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) {
if (!active) {
return 0.0f;
}
float sample = 0.0f;
// SEMI-CONFIRMED: From sample analysis:
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
// This gives results within +/- 2 at the output (before any DAC bitshifting)
// when sustaining at levels 156 - 255 with no modifiers.
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
// positive amps, so negative still needs to be explored, as well as lower levels.
//
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
float amp = EXP2F(ampVal / -1024.0f / 4096.0f);
float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE;
if (isPCMWave()) {
// Render PCM waveform
int len = pcmWaveLength;
int intPCMPosition = int(pcmPosition);
if (intPCMPosition >= len && !pcmWaveLooped) {
// We're now past the end of a non-looping PCM waveform so it's time to die.
deactivate();
return 0.0f;
}
float positionDelta = freq * 2048.0f / SAMPLE_RATE;
// Linear interpolation
float firstSample = getPCMSample(intPCMPosition);
// We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
// It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
// is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
if (pcmWaveInterpolated) {
sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition);
} else {
sample = firstSample;
}
float newPCMPosition = pcmPosition + positionDelta;
if (pcmWaveLooped) {
newPCMPosition = fmod(newPCMPosition, float(pcmWaveLength));
}
pcmPosition = newPCMPosition;
} else {
// Render synthesised waveform
wavePos *= lastFreq / freq;
lastFreq = freq;
float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f);
{
//static const float resAmpFactor = EXP2F(-7);
//resAmp = EXP2I(resonance << 10) * resAmpFactor;
}
// The cutoffModifier may not be supposed to be directly added to the cutoff -
// it may for example need to be multiplied in some way.
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
// More research is needed to be sure that this is correct, however.
float cutoffVal = cutoffRampVal / 262144.0f;
if (cutoffVal > MAX_CUTOFF_VALUE) {
cutoffVal = MAX_CUTOFF_VALUE;
}
// Wave length in samples
float waveLen = SAMPLE_RATE / freq;
// Init cosineLen
float cosineLen = 0.5f * waveLen;
if (cutoffVal > MIDDLE_CUTOFF_VALUE) {
cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis
}
// Start playing in center of first cosine segment
// relWavePos is shifted by a half of cosineLen
float relWavePos = wavePos + 0.5f * cosineLen;
if (relWavePos > waveLen) {
relWavePos -= waveLen;
}
// Ratio of positive segment to wave length
float pulseLen = 0.5f;
if (pulseWidth > 128) {
pulseLen = EXP2F((64 - pulseWidth) / 64.0f);
//static const float pulseLenFactor = EXP2F(-192 / 64);
//pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor;
}
pulseLen *= waveLen;
float hLen = pulseLen - cosineLen;
// Ignore pulsewidths too high for given freq
if (hLen < 0.0f) {
hLen = 0.0f;
}
// Ignore pulsewidths too high for given freq and cutoff
float lLen = waveLen - hLen - 2 * cosineLen;
if (lLen < 0.0f) {
lLen = 0.0f;
}
// Correct resAmp for cutoff in range 50..66
if ((cutoffVal >= MIDDLE_CUTOFF_VALUE) && (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE)) {
resAmp *= sin(FLOAT_PI * (cutoffVal - MIDDLE_CUTOFF_VALUE) / 32.0f);
}
// Produce filtered square wave with 2 cosine waves on slopes
// 1st cosine segment
if (relWavePos < cosineLen) {
sample = -cos(FLOAT_PI * relWavePos / cosineLen);
} else
// high linear segment
if (relWavePos < (cosineLen + hLen)) {
sample = 1.f;
} else
// 2nd cosine segment
if (relWavePos < (2 * cosineLen + hLen)) {
sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
} else {
// low linear segment
sample = -1.f;
}
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
// Attenuate samples below cutoff 50
// Found by sample analysis
sample *= EXP2F(-0.125f * (MIDDLE_CUTOFF_VALUE - cutoffVal));
} else {
// Add resonance sine. Effective for cutoff > 50 only
float resSample = 1.0f;
// Resonance decay speed factor
float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2];
// Now relWavePos counts from the middle of first cosine
relWavePos = wavePos;
// negative segments
if (!(relWavePos < (cosineLen + hLen))) {
resSample = -resSample;
relWavePos -= cosineLen + hLen;
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
resAmpDecayFactor += 0.25f;
}
// Resonance sine WG
resSample *= sin(FLOAT_PI * relWavePos / cosineLen);
// Resonance sine amp
float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact
float resAmpFade = EXP2F(resAmpFadeLog2);
// Now relWavePos set negative to the left from center of any cosine
relWavePos = wavePos;
// negative segment
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
relWavePos -= waveLen;
} else
// positive segment
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
relWavePos -= cosineLen + hLen;
}
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
if (relWavePos < 0.5f * cosineLen) {
float syncSine = sin(FLOAT_PI * relWavePos / cosineLen);
if (relWavePos < 0.0f) {
// The window is synchronous square sine here
resAmpFade *= syncSine * syncSine;
} else {
// The window is synchronous sine here
resAmpFade *= syncSine;
}
}
sample += resSample * resAmp * resAmpFade;
}
// sawtooth waves
if (sawtoothWaveform) {
sample *= cos(FLOAT_2PI * wavePos / waveLen);
}
wavePos++;
// wavePos isn't supposed to be > waveLen
if (wavePos > waveLen) {
wavePos -= waveLen;
}
}
// Multiply sample with current TVA value
sample *= amp;
return sample;
}
void LA32FloatWaveGenerator::deactivate() {
active = false;
}
bool LA32FloatWaveGenerator::isActive() const {
return active;
}
bool LA32FloatWaveGenerator::isPCMWave() const {
return pcmWaveAddress != NULL;
}
void LA32FloatPartialPair::init(const bool useRingModulated, const bool useMixed) {
ringModulated = useRingModulated;
mixed = useMixed;
masterOutputSample = 0.0f;
slaveOutputSample = 0.0f;
}
void LA32FloatPartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
if (useMaster == MASTER) {
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
} else {
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
}
}
void LA32FloatPartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
if (useMaster == MASTER) {
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
} else {
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
}
}
void LA32FloatPartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
if (useMaster == MASTER) {
masterOutputSample = master.generateNextSample(amp, pitch, cutoff);
} else {
slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff);
}
}
static inline float produceDistortedSample(float sample) {
if (sample < -1.0f) {
return sample + 2.0f;
} else if (1.0f < sample) {
return sample - 2.0f;
}
return sample;
}
float LA32FloatPartialPair::nextOutSample() {
if (!ringModulated) {
return masterOutputSample + slaveOutputSample;
}
/*
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
*/
float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample);
return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample;
}
void LA32FloatPartialPair::deactivate(const PairType useMaster) {
if (useMaster == MASTER) {
master.deactivate();
masterOutputSample = 0.0f;
} else {
slave.deactivate();
slaveOutputSample = 0.0f;
}
}
bool LA32FloatPartialPair::isActive(const PairType useMaster) const {
return useMaster == MASTER ? master.isActive() : slave.isActive();
}
} // namespace MT32Emu

View File

@@ -0,0 +1,132 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H
#define MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H
#include "globals.h"
#include "internals.h"
#include "Types.h"
#include "LA32WaveGenerator.h"
namespace MT32Emu {
/**
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
* The output square wave is created by adding high / low linear segments in-between
* the rising and falling cosine segments. Basically, it's very similar to the phase distortion synthesis.
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
*/
class LA32FloatWaveGenerator {
//***************************************************************************
// The local copy of partial parameters below
//***************************************************************************
bool active;
// True means the resulting square wave is to be multiplied by the synchronous cosine
bool sawtoothWaveform;
// Values in range [1..31]
// Value 1 correspong to the minimum resonance
Bit8u resonance;
// Processed value in range [0..255]
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
Bit8u pulseWidth;
// Logarithmic PCM sample start address
const Bit16s *pcmWaveAddress;
// Logarithmic PCM sample length
Bit32u pcmWaveLength;
// true for looped logarithmic PCM samples
bool pcmWaveLooped;
// false for slave PCM partials in the structures with the ring modulation
bool pcmWaveInterpolated;
//***************************************************************************
// Internal variables below
//***************************************************************************
float wavePos;
float lastFreq;
float pcmPosition;
float getPCMSample(unsigned int position);
public:
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Deactivate the WG engine
void deactivate();
// Return active state of the WG engine
bool isActive() const;
// Return true if the WG engine generates PCM wave samples
bool isPCMWave() const;
}; // class LA32FloatWaveGenerator
class LA32FloatPartialPair : public LA32PartialPair {
LA32FloatWaveGenerator master;
LA32FloatWaveGenerator slave;
bool ringModulated;
bool mixed;
float masterOutputSample;
float slaveOutputSample;
public:
// ringModulated should be set to false for the structures with mixing or stereo output
// ringModulated should be set to true for the structures with ring modulation
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
void init(const bool ringModulated, const bool mixed);
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Perform mixing / ring modulation and return the result
float nextOutSample();
// Deactivate the WG engine
void deactivate(const PairType master);
// Return active state of the WG engine
bool isActive(const PairType master) const;
}; // class LA32FloatPartialPair
} // namespace MT32Emu
#endif // #ifndef MT32EMU_LA32_FLOAT_WAVE_GENERATOR_H

155
src/SOUND/munt/LA32Ramp.cpp Normal file
View File

@@ -0,0 +1,155 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Some notes on this class:
This emulates the LA-32's implementation of "ramps". A ramp in this context is a smooth transition from one value to another, handled entirely within the LA-32.
The LA-32 provides this feature for amplitude and filter cutoff values.
The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
(1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
(2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
Once the ramp target value has been hit, the LA-32 raises an interrupt.
Note that the starting point of the ramp is whatever internal value the LA-32 had when the registers were set. This is usually the end point of a previously completed ramp.
Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
Here's what we're pretty confident about:
- The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
Set means downward, clear means upward.
- The lower 7 bits of "increment" indicate how quickly "current" should be changed.
- If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
- Otherwise, if the MSb is set:
- If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
- Otherwise, "current" is gradually reduced (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
- Otherwise (the MSb is unset):
- If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
- Otherwise, "current" is gradually increased (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
We haven't fully explored:
- Values when ramping between levels (though this is probably correct).
- Transition timing (may not be 100% accurate, especially for very fast ramps).
*/
#include "internals.h"
#include "LA32Ramp.h"
#include "Tables.h"
namespace MT32Emu {
// SEMI-CONFIRMED from sample analysis.
const int TARGET_MULT = 0x40000;
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
// We simulate the delay in handling "target was reached" interrupts by waiting
// this many samples before setting interruptRaised.
// FIXME: This should vary with the sample rate, but doesn't.
// SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
// and the 8095, a good value is hard to pin down.
// This one matches observed behaviour on a few digital captures I had handy,
// and should be double-checked. We may also need a more sophisticated delay
// scheme eventually.
const int INTERRUPT_TIME = 7;
LA32Ramp::LA32Ramp() :
current(0),
largeTarget(0),
largeIncrement(0),
interruptCountdown(0),
interruptRaised(false) {
}
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
// CONFIRMED: From sample analysis, this appears to be very accurate.
if (increment == 0) {
largeIncrement = 0;
} else {
// Three bits in the fractional part, no need to interpolate
// (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f)
Bit32u expArg = increment & 0x7F;
largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511];
largeIncrement <<= expArg >> 3;
largeIncrement += 64;
largeIncrement >>= 9;
}
descending = (increment & 0x80) != 0;
if (descending) {
// CONFIRMED: From sample analysis, descending increments are slightly faster
largeIncrement++;
}
largeTarget = target * TARGET_MULT;
interruptCountdown = 0;
interruptRaised = false;
}
Bit32u LA32Ramp::nextValue() {
if (interruptCountdown > 0) {
if (--interruptCountdown == 0) {
interruptRaised = true;
}
} else if (largeIncrement != 0) {
// CONFIRMED from sample analysis: When increment is 0, the LA32 does *not* change the current value at all (and of course doesn't fire an interrupt).
if (descending) {
// Lowering current value
if (largeIncrement > current) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
} else {
current -= largeIncrement;
if (current <= largeTarget) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
}
}
} else {
// Raising current value
if (MAX_CURRENT - current < largeIncrement) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
} else {
current += largeIncrement;
if (current >= largeTarget) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
}
}
}
}
return current;
}
bool LA32Ramp::checkInterrupt() {
bool wasRaised = interruptRaised;
interruptRaised = false;
return wasRaised;
}
void LA32Ramp::reset() {
current = 0;
largeTarget = 0;
largeIncrement = 0;
descending = false;
interruptCountdown = 0;
interruptRaised = false;
}
} // namespace MT32Emu

46
src/SOUND/munt/LA32Ramp.h Normal file
View File

@@ -0,0 +1,46 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_LA32RAMP_H
#define MT32EMU_LA32RAMP_H
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
class LA32Ramp {
private:
Bit32u current;
unsigned int largeTarget;
unsigned int largeIncrement;
bool descending;
int interruptCountdown;
bool interruptRaised;
public:
LA32Ramp();
void startRamp(Bit8u target, Bit8u increment);
Bit32u nextValue();
bool checkInterrupt();
void reset();
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_LA32RAMP_H

View File

@@ -0,0 +1,422 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "internals.h"
#include "LA32WaveGenerator.h"
#include "Tables.h"
namespace MT32Emu {
static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18;
static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18;
static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18;
static const Bit32u MAX_CUTOFF_VALUE = 240 << 18;
static const LogSample SILENCE = {65535, LogSample::POSITIVE};
Bit16u LA32Utilites::interpolateExp(const Bit16u fract) {
Bit16u expTabIndex = fract >> 3;
Bit16u extraBits = ~fract & 7;
Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex];
Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]);
return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3);
}
Bit16s LA32Utilites::unlog(const LogSample &logSample) {
//Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f);
Bit32u intLogValue = logSample.logValue >> 12;
Bit16u fracLogValue = logSample.logValue & 4095;
Bit16s sample = interpolateExp(fracLogValue) >> intLogValue;
return logSample.sign == LogSample::POSITIVE ? sample : -sample;
}
void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) {
Bit32u logSampleValue = logSample1.logValue + logSample2.logValue;
logSample1.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
Bit32u LA32WaveGenerator::getSampleStep() {
// sampleStep = EXP2F(pitch / 4096.0f + 4.0f)
Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
sampleStep <<= pitch >> 12;
sampleStep >>= 8;
sampleStep &= ~1;
return sampleStep;
}
Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) {
// resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f);
Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095);
resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12;
return resonanceWaveLengthFactor;
}
Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) {
// Ratio of positive segment to wave length
Bit32u effectivePulseWidthValue = 0;
if (pulseWidth > 128) {
effectivePulseWidthValue = (pulseWidth - 128) << 6;
}
Bit32u highLinearLength = 0;
// highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH;
if (effectivePulseWidthValue < effectiveCutoffValue) {
Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue;
highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095);
highLinearLength <<= 7 + (expArg >> 12);
highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH;
}
return highLinearLength;
}
void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) {
// Assuming 12-bit multiplication used here
squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4);
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = POSITIVE_RISING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
if (squareWavePosition < highLinearLength) {
phase = POSITIVE_LINEAR_SEGMENT;
return;
}
squareWavePosition -= highLinearLength;
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = POSITIVE_FALLING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
resonanceSinePosition = squareWavePosition;
if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) {
phase = NEGATIVE_FALLING_SINE_SEGMENT;
return;
}
squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH;
if (squareWavePosition < lowLinearLength) {
phase = NEGATIVE_LINEAR_SEGMENT;
return;
}
squareWavePosition -= lowLinearLength;
phase = NEGATIVE_RISING_SINE_SEGMENT;
}
void LA32WaveGenerator::advancePosition() {
wavePosition += getSampleStep();
wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH;
Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0;
Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue);
Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue);
Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength;
computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor);
resonancePhase = ResonancePhase(((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3);
}
void LA32WaveGenerator::generateNextSquareWaveLogSample() {
Bit32u logSampleValue;
switch (phase) {
case POSITIVE_RISING_SINE_SEGMENT:
case NEGATIVE_FALLING_SINE_SEGMENT:
logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511];
break;
case POSITIVE_FALLING_SINE_SEGMENT:
case NEGATIVE_RISING_SINE_SEGMENT:
logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511];
break;
case POSITIVE_LINEAR_SEGMENT:
case NEGATIVE_LINEAR_SEGMENT:
default:
logSampleValue = 0;
break;
}
logSampleValue <<= 2;
logSampleValue += amp >> 10;
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9;
}
squareLogSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::generateNextResonanceWaveLogSample() {
Bit32u logSampleValue;
if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) {
logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511];
} else {
logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511];
}
logSampleValue <<= 2;
logSampleValue += amp >> 10;
// From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments
Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1;
// Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not.
logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8);
// To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment
if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) {
// The window is synchronous sine here
logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2;
} else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) {
// The window is synchronous square sine here
logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3;
}
if (cutoffVal < MIDDLE_CUTOFF_VALUE) {
// For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed
logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9);
} else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) {
// For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed
Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13;
logSampleValue += Tables::getInstance().logsin9[sineIx] << 2;
}
// After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures
logSampleValue -= 1 << 12;
resonanceLogSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const {
Bit32u sawtoothCosinePosition = wavePosition + (1 << 18);
if ((sawtoothCosinePosition & (1 << 18)) > 0) {
logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511];
} else {
logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511];
}
logSample.logValue <<= 2;
logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE;
}
void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const {
Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1;
logSampleValue += amp >> 10;
logSample.logValue = logSampleValue < 65536 ? Bit16u(logSampleValue) : 65535;
logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE;
}
void LA32WaveGenerator::generateNextPCMWaveLogSamples() {
// This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc.
// The most probable cause is the factor in the interpolation formula is one bit less
// accurate than the sample position counter
pcmInterpolationFactor = (wavePosition & 255) >> 1;
Bit32u pcmWaveTableIx = wavePosition >> 8;
pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
if (pcmWaveInterpolated) {
pcmWaveTableIx++;
if (pcmWaveTableIx < pcmWaveLength) {
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
} else {
if (pcmWaveLooped) {
pcmWaveTableIx -= pcmWaveLength;
pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]);
} else {
secondPCMLogSample = SILENCE;
}
}
} else {
secondPCMLogSample = SILENCE;
}
// pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f);
Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095);
pcmSampleStep <<= pitch >> 12;
// Seeing the actual lengths of the PCM wave for pitches 00..12,
// the pcmPosition counter can be assumed to have 8-bit fractions
pcmSampleStep >>= 9;
wavePosition += pcmSampleStep;
if (wavePosition >= (pcmWaveLength << 8)) {
if (pcmWaveLooped) {
wavePosition -= pcmWaveLength << 8;
} else {
deactivate();
}
}
}
void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) {
sawtoothWaveform = useSawtoothWaveform;
pulseWidth = usePulseWidth;
resonance = useResonance;
wavePosition = 0;
squareWavePosition = 0;
phase = POSITIVE_RISING_SINE_SEGMENT;
resonanceSinePosition = 0;
resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT;
resonanceAmpSubtraction = (32 - resonance) << 10;
resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2;
pcmWaveAddress = NULL;
active = true;
}
void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) {
pcmWaveAddress = usePCMWaveAddress;
pcmWaveLength = usePCMWaveLength;
pcmWaveLooped = usePCMWaveLooped;
pcmWaveInterpolated = usePCMWaveInterpolated;
wavePosition = 0;
active = true;
}
void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) {
if (!active) {
return;
}
amp = useAmp;
pitch = usePitch;
if (isPCMWave()) {
generateNextPCMWaveLogSamples();
return;
}
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
// More research is needed to be sure that this is correct, however.
cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal;
generateNextSquareWaveLogSample();
generateNextResonanceWaveLogSample();
if (sawtoothWaveform) {
LogSample cosineLogSample;
generateNextSawtoothCosineLogSample(cosineLogSample);
LA32Utilites::addLogSamples(squareLogSample, cosineLogSample);
LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample);
}
advancePosition();
}
LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const {
if (!isActive()) {
return SILENCE;
}
if (isPCMWave()) {
return first ? firstPCMLogSample : secondPCMLogSample;
}
return first ? squareLogSample : resonanceLogSample;
}
void LA32WaveGenerator::deactivate() {
active = false;
}
bool LA32WaveGenerator::isActive() const {
return active;
}
bool LA32WaveGenerator::isPCMWave() const {
return pcmWaveAddress != NULL;
}
Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const {
return pcmInterpolationFactor;
}
void LA32IntPartialPair::init(const bool useRingModulated, const bool useMixed) {
ringModulated = useRingModulated;
mixed = useMixed;
}
void LA32IntPartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) {
if (useMaster == MASTER) {
master.initSynth(sawtoothWaveform, pulseWidth, resonance);
} else {
slave.initSynth(sawtoothWaveform, pulseWidth, resonance);
}
}
void LA32IntPartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) {
if (useMaster == MASTER) {
master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true);
} else {
slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated);
}
}
void LA32IntPartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) {
if (useMaster == MASTER) {
master.generateNextSample(amp, pitch, cutoff);
} else {
slave.generateNextSample(amp, pitch, cutoff);
}
}
Bit16s LA32IntPartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) {
if (!wg.isActive()) {
return 0;
}
Bit16s firstSample = LA32Utilites::unlog(wg.getOutputLogSample(true));
Bit16s secondSample = LA32Utilites::unlog(wg.getOutputLogSample(false));
if (wg.isPCMWave()) {
return Bit16s(firstSample + (((Bit32s(secondSample) - Bit32s(firstSample)) * wg.getPCMInterpolationFactor()) >> 7));
}
return firstSample + secondSample;
}
Bit16s LA32IntPartialPair::nextOutSample() {
if (!ringModulated) {
return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave);
}
/*
* SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion.
* LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191.
* This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case.
* As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space,
* it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication.
* Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning.
*/
Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing
Bit16s masterSample = nonOverdrivenMasterSample << 2;
masterSample >>= 2;
/* SEMI-CONFIRMED from sample analysis:
* We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial.
* It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial
* is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair).
*/
Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave);
slaveSample <<= 2;
slaveSample >>= 2;
Bit16s ringModulatedSample = Bit16s((Bit32s(masterSample) * Bit32s(slaveSample)) >> 13);
return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample;
}
void LA32IntPartialPair::deactivate(const PairType useMaster) {
if (useMaster == MASTER) {
master.deactivate();
} else {
slave.deactivate();
}
}
bool LA32IntPartialPair::isActive(const PairType useMaster) const {
return useMaster == MASTER ? master.isActive() : slave.isActive();
}
} // namespace MT32Emu

View File

@@ -0,0 +1,266 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_LA32_WAVE_GENERATOR_H
#define MT32EMU_LA32_WAVE_GENERATOR_H
#include "globals.h"
#include "internals.h"
#include "Types.h"
namespace MT32Emu {
/**
* LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions
* It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these:
* - interpolation of exponent table (obvious, a delta value has 4 bits)
* - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion)
* - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip)
* and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss
* Subtraction is supposed to be replaced by simple inversion
* As the logarithmic sine is always negative, all the logarithmic values are treated as decrements
*/
struct LogSample {
// 16-bit fixed point value, includes 12-bit fractional part
// 4-bit integer part allows to present any 16-bit sample in the log-space
// Obviously, the log value doesn't contain the sign of the resulting sample
Bit16u logValue;
enum {
POSITIVE,
NEGATIVE
} sign;
};
class LA32Utilites {
public:
static Bit16u interpolateExp(const Bit16u fract);
static Bit16s unlog(const LogSample &logSample);
static void addLogSamples(LogSample &logSample1, const LogSample &logSample2);
};
/**
* LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator.
* The output square wave is created by adding high / low linear segments in-between
* the rising and falling cosine segments. Basically, it's very similar to the phase distortion synthesis.
* Behaviour of a true resonance filter is emulated by adding decaying sine wave.
* The beginning and the ending of the resonant sine is multiplied by a cosine window.
* To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave.
*/
class LA32WaveGenerator {
//***************************************************************************
// The local copy of partial parameters below
//***************************************************************************
bool active;
// True means the resulting square wave is to be multiplied by the synchronous cosine
bool sawtoothWaveform;
// Logarithmic amp of the wave generator
Bit32u amp;
// Logarithmic frequency of the resulting wave
Bit16u pitch;
// Values in range [1..31]
// Value 1 correspong to the minimum resonance
Bit8u resonance;
// Processed value in range [0..255]
// Values in range [0..128] have no effect and the resulting wave remains symmetrical
// Value 255 corresponds to the maximum possible asymmetric of the resulting wave
Bit8u pulseWidth;
// Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier
Bit32u cutoffVal;
// Logarithmic PCM sample start address
const Bit16s *pcmWaveAddress;
// Logarithmic PCM sample length
Bit32u pcmWaveLength;
// true for looped logarithmic PCM samples
bool pcmWaveLooped;
// false for slave PCM partials in the structures with the ring modulation
bool pcmWaveInterpolated;
//***************************************************************************
// Internal variables below
//***************************************************************************
// Relative position within either the synth wave or the PCM sampled wave
// 0 - start of the positive rising sine segment of the square wave or start of the PCM sample
// 1048576 (2^20) - end of the negative rising sine segment of the square wave
// For PCM waves, the address of the currently playing sample equals (wavePosition / 256)
Bit32u wavePosition;
// Relative position within a square wave phase:
// 0 - start of the phase
// 262144 (2^18) - end of a sine phase in the square wave
Bit32u squareWavePosition;
// Relative position within the positive or negative wave segment:
// 0 - start of the corresponding positive or negative segment of the square wave
// 262144 (2^18) - corresponds to end of the first sine phase in the square wave
// The same increment sampleStep is used to indicate the current position
// since the length of the resonance wave is always equal to four square wave sine segments.
Bit32u resonanceSinePosition;
// The amp of the resonance sine wave grows with the resonance value
// As the resonance value cannot change while the partial is active, it is initialised once
Bit32u resonanceAmpSubtraction;
// The decay speed of resonance sine wave, depends on the resonance value
Bit32u resAmpDecayFactor;
// Fractional part of the pcmPosition
Bit32u pcmInterpolationFactor;
// Current phase of the square wave
enum {
POSITIVE_RISING_SINE_SEGMENT,
POSITIVE_LINEAR_SEGMENT,
POSITIVE_FALLING_SINE_SEGMENT,
NEGATIVE_FALLING_SINE_SEGMENT,
NEGATIVE_LINEAR_SEGMENT,
NEGATIVE_RISING_SINE_SEGMENT
} phase;
// Current phase of the resonance wave
enum ResonancePhase {
POSITIVE_RISING_RESONANCE_SINE_SEGMENT,
POSITIVE_FALLING_RESONANCE_SINE_SEGMENT,
NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT,
NEGATIVE_RISING_RESONANCE_SINE_SEGMENT
} resonancePhase;
// Resulting log-space samples of the square and resonance waves
LogSample squareLogSample;
LogSample resonanceLogSample;
// Processed neighbour log-space samples of the PCM wave
LogSample firstPCMLogSample;
LogSample secondPCMLogSample;
//***************************************************************************
// Internal methods below
//***************************************************************************
Bit32u getSampleStep();
Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue);
Bit32u getHighLinearLength(Bit32u effectiveCutoffValue);
void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor);
void advancePosition();
void generateNextSquareWaveLogSample();
void generateNextResonanceWaveLogSample();
void generateNextSawtoothCosineLogSample(LogSample &logSample) const;
void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const;
void generateNextPCMWaveLogSamples();
public:
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards
LogSample getOutputLogSample(const bool first) const;
// Deactivate the WG engine
void deactivate();
// Return active state of the WG engine
bool isActive() const;
// Return true if the WG engine generates PCM wave samples
bool isPCMWave() const;
// Return current PCM interpolation factor
Bit32u getPCMInterpolationFactor() const;
}; // class LA32WaveGenerator
// LA32PartialPair contains a structure of two partials being mixed / ring modulated
class LA32PartialPair {
public:
enum PairType {
MASTER,
SLAVE
};
virtual ~LA32PartialPair() {}
// ringModulated should be set to false for the structures with mixing or stereo output
// ringModulated should be set to true for the structures with ring modulation
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
virtual void init(const bool ringModulated, const bool mixed) = 0;
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
virtual void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) = 0;
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
virtual void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) = 0;
// Deactivate the WG engine
virtual void deactivate(const PairType master) = 0;
}; // class LA32PartialPair
class LA32IntPartialPair : public LA32PartialPair {
LA32WaveGenerator master;
LA32WaveGenerator slave;
bool ringModulated;
bool mixed;
static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg);
public:
// ringModulated should be set to false for the structures with mixing or stereo output
// ringModulated should be set to true for the structures with ring modulation
// mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output
void init(const bool ringModulated, const bool mixed);
// Initialise the WG engine for generation of synth partial samples and set up the invariant parameters
void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance);
// Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters
void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped);
// Update parameters with respect to TVP, TVA and TVF, and generate next sample
void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff);
// Perform mixing / ring modulation of WG output and return the result
// Although, LA32 applies panning itself, we assume it is applied in the mixer, not within a pair
Bit16s nextOutSample();
// Deactivate the WG engine
void deactivate(const PairType master);
// Return active state of the WG engine
bool isActive(const PairType master) const;
}; // class LA32IntPartialPair
} // namespace MT32Emu
#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H

View File

@@ -0,0 +1,132 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MEMORY_REGION_H
#define MT32EMU_MEMORY_REGION_H
#include <cstddef>
#include "globals.h"
#include "Types.h"
#include "Structures.h"
namespace MT32Emu {
enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
class Synth;
class MemoryRegion {
private:
Synth *synth;
Bit8u *realMemory;
Bit8u *maxTable;
public:
MemoryRegionType type;
Bit32u startAddr, entrySize, entries;
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
synth = useSynth;
realMemory = useRealMemory;
maxTable = useMaxTable;
type = useType;
startAddr = useStartAddr;
entrySize = useEntrySize;
entries = useEntries;
}
int lastTouched(Bit32u addr, Bit32u len) const {
return (offset(addr) + len - 1) / entrySize;
}
int firstTouchedOffset(Bit32u addr) const {
return offset(addr) % entrySize;
}
int firstTouched(Bit32u addr) const {
return offset(addr) / entrySize;
}
Bit32u regionEnd() const {
return startAddr + entrySize * entries;
}
bool contains(Bit32u addr) const {
return addr >= startAddr && addr < regionEnd();
}
int offset(Bit32u addr) const {
return addr - startAddr;
}
Bit32u getClampedLen(Bit32u addr, Bit32u len) const {
if (addr + len > regionEnd())
return regionEnd() - addr;
return len;
}
Bit32u next(Bit32u addr, Bit32u len) const {
if (addr + len > regionEnd()) {
return regionEnd() - addr;
}
return 0;
}
Bit8u getMaxValue(int off) const {
if (maxTable == NULL)
return 0xFF;
return maxTable[off % entrySize];
}
Bit8u *getRealMemory() const {
return realMemory;
}
bool isReadable() const {
return getRealMemory() != NULL;
}
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
}; // class MemoryRegion
class PatchTempMemoryRegion : public MemoryRegion {
public:
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
};
class RhythmTempMemoryRegion : public MemoryRegion {
public:
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
};
class TimbreTempMemoryRegion : public MemoryRegion {
public:
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
};
class PatchesMemoryRegion : public MemoryRegion {
public:
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
};
class TimbresMemoryRegion : public MemoryRegion {
public:
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
};
class SystemMemoryRegion : public MemoryRegion {
public:
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
};
class DisplayMemoryRegion : public MemoryRegion {
public:
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), SYSEX_BUFFER_SIZE - 1, 1) {}
};
class ResetMemoryRegion : public MemoryRegion {
public:
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_MEMORY_REGION_H

View File

@@ -0,0 +1,71 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MIDI_EVENT_QUEUE_H
#define MT32EMU_MIDI_EVENT_QUEUE_H
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
/**
* Used to safely store timestamped MIDI events in a local queue.
*/
struct MidiEvent {
Bit32u shortMessageData;
const Bit8u *sysexData;
Bit32u sysexLength;
Bit32u timestamp;
~MidiEvent();
void setShortMessage(Bit32u shortMessageData, Bit32u timestamp);
void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
};
/**
* Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it.
* It is intended to:
* - get rid of prerenderer while retaining graceful partial abortion
* - add fair emulation of the MIDI interface delays
* - extend the synth interface with the default implementation of a typical rendering loop.
* THREAD SAFETY:
* It is safe to use either in a single thread environment or when there are only two threads - one performs only reading
* and one performs only writing. More complicated usage requires external synchronisation.
*/
class MidiEventQueue {
private:
MidiEvent * const ringBuffer;
const Bit32u ringBufferMask;
volatile Bit32u startPosition;
volatile Bit32u endPosition;
public:
MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); // Must be a power of 2
~MidiEventQueue();
void reset();
bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp);
bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
const MidiEvent *peekMidiEvent();
void dropMidiEvent();
bool isFull() const;
bool inline isEmpty() const;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_MIDI_EVENT_QUEUE_H

View File

@@ -0,0 +1,289 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cstring>
#include "internals.h"
#include "MidiStreamParser.h"
#include "Synth.h"
using namespace MT32Emu;
DefaultMidiStreamParser::DefaultMidiStreamParser(Synth &useSynth, Bit32u initialStreamBufferCapacity) :
MidiStreamParser(initialStreamBufferCapacity), synth(useSynth), timestampSet(false) {}
void DefaultMidiStreamParser::setTimestamp(const Bit32u useTimestamp) {
timestampSet = true;
timestamp = useTimestamp;
}
void DefaultMidiStreamParser::resetTimestamp() {
timestampSet = false;
}
void DefaultMidiStreamParser::handleShortMessage(const Bit32u message) {
do {
if (timestampSet) {
if (synth.playMsg(message, timestamp)) return;
}
else {
if (synth.playMsg(message)) return;
}
} while (synth.reportHandler->onMIDIQueueOverflow());
}
void DefaultMidiStreamParser::handleSysex(const Bit8u *stream, const Bit32u length) {
do {
if (timestampSet) {
if (synth.playSysex(stream, length, timestamp)) return;
}
else {
if (synth.playSysex(stream, length)) return;
}
} while (synth.reportHandler->onMIDIQueueOverflow());
}
void DefaultMidiStreamParser::handleSystemRealtimeMessage(const Bit8u realtime) {
synth.reportHandler->onMIDISystemRealtime(realtime);
}
void DefaultMidiStreamParser::printDebug(const char *debugMessage) {
synth.printDebug("%s", debugMessage);
}
MidiStreamParser::MidiStreamParser(Bit32u initialStreamBufferCapacity) :
MidiStreamParserImpl(*this, *this, initialStreamBufferCapacity) {}
MidiStreamParserImpl::MidiStreamParserImpl(MidiReceiver &useReceiver, MidiReporter &useReporter, Bit32u initialStreamBufferCapacity) :
midiReceiver(useReceiver), midiReporter(useReporter)
{
if (initialStreamBufferCapacity < SYSEX_BUFFER_SIZE) initialStreamBufferCapacity = SYSEX_BUFFER_SIZE;
if (MAX_STREAM_BUFFER_SIZE < initialStreamBufferCapacity) initialStreamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
streamBufferCapacity = initialStreamBufferCapacity;
streamBuffer = new Bit8u[streamBufferCapacity];
streamBufferSize = 0;
runningStatus = 0;
reserved = NULL;
}
MidiStreamParserImpl::~MidiStreamParserImpl() {
delete[] streamBuffer;
}
void MidiStreamParserImpl::parseStream(const Bit8u *stream, Bit32u length) {
while (length > 0) {
Bit32u parsedMessageLength = 0;
if (0xF8 <= *stream) {
// Process System Realtime immediately and go on
midiReceiver.handleSystemRealtimeMessage(*stream);
parsedMessageLength = 1;
// No effect on the running status
} else if (streamBufferSize > 0) {
// Check if there is something in streamBuffer waiting for being processed
if (*streamBuffer == 0xF0) {
parsedMessageLength = parseSysexFragment(stream, length);
} else {
parsedMessageLength = parseShortMessageDataBytes(stream, length);
}
} else {
if (*stream == 0xF0) {
runningStatus = 0; // SysEx clears the running status
parsedMessageLength = parseSysex(stream, length);
} else {
parsedMessageLength = parseShortMessageStatus(stream);
}
}
// Parsed successfully
stream += parsedMessageLength;
length -= parsedMessageLength;
}
}
void MidiStreamParserImpl::processShortMessage(const Bit32u message) {
// Adds running status to the MIDI message if it doesn't contain one
Bit8u status = Bit8u(message);
if (0xF8 <= status) {
midiReceiver.handleSystemRealtimeMessage(status);
} else if (processStatusByte(status)) {
midiReceiver.handleShortMessage((message << 8) | status);
} else if (0x80 <= status) { // If no running status available yet, skip this message
midiReceiver.handleShortMessage(message);
}
}
// We deal with SysEx messages below 512 bytes long in most cases. Nevertheless, it seems reasonable to support a possibility
// to load bulk dumps using a single message. However, this is known to fail with a real device due to limited input buffer size.
bool MidiStreamParserImpl::checkStreamBufferCapacity(const bool preserveContent) {
if (streamBufferSize < streamBufferCapacity) return true;
if (streamBufferCapacity < MAX_STREAM_BUFFER_SIZE) {
Bit8u *oldStreamBuffer = streamBuffer;
streamBufferCapacity = MAX_STREAM_BUFFER_SIZE;
streamBuffer = new Bit8u[streamBufferCapacity];
if (preserveContent) memcpy(streamBuffer, oldStreamBuffer, streamBufferSize);
delete[] oldStreamBuffer;
return true;
}
return false;
}
// Checks input byte whether it is a status byte. If not, replaces it with running status when available.
// Returns true if the input byte was changed to running status.
bool MidiStreamParserImpl::processStatusByte(Bit8u &status) {
if (status < 0x80) {
// First byte isn't status, try running status
if (runningStatus < 0x80) {
// No running status available yet
midiReporter.printDebug("processStatusByte: No valid running status yet, MIDI message ignored");
return false;
}
status = runningStatus;
return true;
} else if (status < 0xF0) {
// Store current status as running for a Voice message
runningStatus = status;
} else if (status < 0xF8) {
// System Common clears running status
runningStatus = 0;
} // System Realtime doesn't affect running status
return false;
}
// Returns # of bytes parsed
Bit32u MidiStreamParserImpl::parseShortMessageStatus(const Bit8u stream[]) {
Bit8u status = *stream;
Bit32u parsedLength = processStatusByte(status) ? 0 : 1;
if (0x80 <= status) { // If no running status available yet, skip one byte
*streamBuffer = status;
++streamBufferSize;
}
return parsedLength;
}
// Returns # of bytes parsed
Bit32u MidiStreamParserImpl::parseShortMessageDataBytes(const Bit8u stream[], Bit32u length) {
const Bit32u shortMessageLength = Synth::getShortMessageLength(*streamBuffer);
Bit32u parsedLength = 0;
// Append incoming bytes to streamBuffer
while ((streamBufferSize < shortMessageLength) && (length-- > 0)) {
Bit8u dataByte = *(stream++);
if (dataByte < 0x80) {
// Add data byte to streamBuffer
streamBuffer[streamBufferSize++] = dataByte;
} else if (dataByte < 0xF8) {
// Discard invalid bytes and start over
char s[128];
sprintf(s, "parseShortMessageDataBytes: Invalid short message: status %02x, expected length %i, actual %i -> ignored", *streamBuffer, shortMessageLength, streamBufferSize);
midiReporter.printDebug(s);
streamBufferSize = 0; // Clear streamBuffer
return parsedLength;
} else {
// Bypass System Realtime message
midiReceiver.handleSystemRealtimeMessage(dataByte);
}
++parsedLength;
}
if (streamBufferSize < shortMessageLength) return parsedLength; // Still lacks data bytes
// Assemble short message
Bit32u shortMessage = streamBuffer[0];
for (Bit32u i = 1; i < shortMessageLength; ++i) {
shortMessage |= streamBuffer[i] << (i << 3);
}
midiReceiver.handleShortMessage(shortMessage);
streamBufferSize = 0; // Clear streamBuffer
return parsedLength;
}
// Returns # of bytes parsed
Bit32u MidiStreamParserImpl::parseSysex(const Bit8u stream[], const Bit32u length) {
// Find SysEx length
Bit32u sysexLength = 1;
while (sysexLength < length) {
Bit8u nextByte = stream[sysexLength++];
if (0x80 <= nextByte) {
if (nextByte == 0xF7) {
// End of SysEx
midiReceiver.handleSysex(stream, sysexLength);
return sysexLength;
}
if (0xF8 <= nextByte) {
// The System Realtime message must be processed right after return
// but the SysEx is actually fragmented and to be reconstructed in streamBuffer
--sysexLength;
break;
}
// Illegal status byte in SysEx message, aborting
midiReporter.printDebug("parseSysex: SysEx message lacks end-of-sysex (0xf7), ignored");
// Continue parsing from that point
return sysexLength - 1;
}
}
// Store incomplete SysEx message for further processing
streamBufferSize = sysexLength;
if (checkStreamBufferCapacity(false)) {
memcpy(streamBuffer, stream, sysexLength);
} else {
// Not enough buffer capacity, don't care about the real buffer content, just mark the first byte
*streamBuffer = *stream;
streamBufferSize = streamBufferCapacity;
}
return sysexLength;
}
// Returns # of bytes parsed
Bit32u MidiStreamParserImpl::parseSysexFragment(const Bit8u stream[], const Bit32u length) {
Bit32u parsedLength = 0;
while (parsedLength < length) {
Bit8u nextByte = stream[parsedLength++];
if (nextByte < 0x80) {
// Add SysEx data byte to streamBuffer
if (checkStreamBufferCapacity(true)) streamBuffer[streamBufferSize++] = nextByte;
continue;
}
if (0xF8 <= nextByte) {
// Bypass System Realtime message
midiReceiver.handleSystemRealtimeMessage(nextByte);
continue;
}
if (nextByte != 0xF7) {
// Illegal status byte in SysEx message, aborting
midiReporter.printDebug("parseSysexFragment: SysEx message lacks end-of-sysex (0xf7), ignored");
// Clear streamBuffer and continue parsing from that point
streamBufferSize = 0;
--parsedLength;
break;
}
// End of SysEx
if (checkStreamBufferCapacity(true)) {
streamBuffer[streamBufferSize++] = nextByte;
midiReceiver.handleSysex(streamBuffer, streamBufferSize);
streamBufferSize = 0; // Clear streamBuffer
break;
}
// Encountered streamBuffer overrun
midiReporter.printDebug("parseSysexFragment: streamBuffer overrun while receiving SysEx message, ignored. Max allowed size of fragmented SysEx is 32768 bytes.");
streamBufferSize = 0; // Clear streamBuffer
break;
}
return parsedLength;
}

View File

@@ -0,0 +1,124 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MIDI_STREAM_PARSER_H
#define MT32EMU_MIDI_STREAM_PARSER_H
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
class Synth;
// Interface for a user-supplied class to receive parsed well-formed MIDI messages.
class MT32EMU_EXPORT MidiReceiver {
public:
// Invoked when a complete short MIDI message is parsed in the input MIDI stream.
virtual void handleShortMessage(const Bit32u message) = 0;
// Invoked when a complete well-formed System Exclusive MIDI message is parsed in the input MIDI stream.
virtual void handleSysex(const Bit8u stream[], const Bit32u length) = 0;
// Invoked when a System Realtime MIDI message is parsed in the input MIDI stream.
virtual void handleSystemRealtimeMessage(const Bit8u realtime) = 0;
protected:
~MidiReceiver() {}
};
// Interface for a user-supplied class to receive notifications of input MIDI stream parse errors.
class MT32EMU_EXPORT MidiReporter {
public:
// Invoked when an error occurs during processing the input MIDI stream.
virtual void printDebug(const char *debugMessage) = 0;
protected:
~MidiReporter() {}
};
// Provides a context for parsing a stream of MIDI events coming from a single source.
// There can be multiple MIDI sources feeding MIDI events to a single Synth object.
// NOTE: Calls from multiple threads which feed a single Synth object with data must be explicitly synchronised,
// although, no synchronisation is required with the rendering thread.
class MT32EMU_EXPORT MidiStreamParserImpl {
public:
// The first two arguments provide for implementations of essential interfaces needed.
// The third argument specifies streamBuffer initial capacity. The buffer capacity should be large enough to fit the longest SysEx expected.
// If a longer SysEx occurs, streamBuffer is reallocated to the maximum size of MAX_STREAM_BUFFER_SIZE (32768 bytes).
// Default capacity is SYSEX_BUFFER_SIZE (1000 bytes) which is enough to fit SysEx messages in common use.
MidiStreamParserImpl(MidiReceiver &, MidiReporter &, Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
virtual ~MidiStreamParserImpl();
// Parses a block of raw MIDI bytes. All the parsed MIDI messages are sent in sequence to the user-supplied methods for further processing.
// SysEx messages are allowed to be fragmented across several calls to this method. Running status is also handled for short messages.
// NOTE: the total length of a SysEx message being fragmented shall not exceed MAX_STREAM_BUFFER_SIZE (32768 bytes).
void parseStream(const Bit8u *stream, Bit32u length);
// Convenience method which accepts a Bit32u-encoded short MIDI message and sends it to the user-supplied method for further processing.
// The short MIDI message may contain no status byte, the running status is used in this case.
void processShortMessage(const Bit32u message);
private:
Bit8u runningStatus;
Bit8u *streamBuffer;
Bit32u streamBufferCapacity;
Bit32u streamBufferSize;
MidiReceiver &midiReceiver;
MidiReporter &midiReporter;
// Binary compatibility helper.
void *reserved;
bool checkStreamBufferCapacity(const bool preserveContent);
bool processStatusByte(Bit8u &status);
Bit32u parseShortMessageStatus(const Bit8u stream[]);
Bit32u parseShortMessageDataBytes(const Bit8u stream[], Bit32u length);
Bit32u parseSysex(const Bit8u stream[], const Bit32u length);
Bit32u parseSysexFragment(const Bit8u stream[], const Bit32u length);
}; // class MidiStreamParserImpl
// An abstract class that provides a context for parsing a stream of MIDI events coming from a single source.
class MT32EMU_EXPORT MidiStreamParser : public MidiStreamParserImpl, protected MidiReceiver, protected MidiReporter {
public:
// The argument specifies streamBuffer initial capacity. The buffer capacity should be large enough to fit the longest SysEx expected.
// If a longer SysEx occurs, streamBuffer is reallocated to the maximum size of MAX_STREAM_BUFFER_SIZE (32768 bytes).
// Default capacity is SYSEX_BUFFER_SIZE (1000 bytes) which is enough to fit SysEx messages in common use.
explicit MidiStreamParser(Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
};
class MT32EMU_EXPORT DefaultMidiStreamParser : public MidiStreamParser {
public:
explicit DefaultMidiStreamParser(Synth &synth, Bit32u initialStreamBufferCapacity = SYSEX_BUFFER_SIZE);
void setTimestamp(const Bit32u useTimestamp);
void resetTimestamp();
protected:
void handleShortMessage(const Bit32u message);
void handleSysex(const Bit8u *stream, const Bit32u length);
void handleSystemRealtimeMessage(const Bit8u realtime);
void printDebug(const char *debugMessage);
private:
Synth &synth;
bool timestampSet;
Bit32u timestamp;
};
} // namespace MT32Emu
#endif // MT32EMU_MIDI_STREAM_PARSER_H

695
src/SOUND/munt/Part.cpp Normal file
View File

@@ -0,0 +1,695 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cstring>
#include "internals.h"
#include "Part.h"
#include "Partial.h"
#include "PartialManager.h"
#include "Poly.h"
#include "Synth.h"
namespace MT32Emu {
static const Bit8u PartialStruct[13] = {
0, 0, 2, 2, 1, 3,
3, 0, 3, 0, 2, 1, 3
};
static const Bit8u PartialMixStruct[13] = {
0, 1, 0, 1, 1, 0,
1, 3, 3, 2, 2, 2, 2
};
RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
strcpy(name, "Rhythm");
rhythmTemp = &synth->mt32ram.rhythmTemp[0];
refresh();
}
Part::Part(Synth *useSynth, unsigned int usePartNum) {
synth = useSynth;
partNum = usePartNum;
patchCache[0].dirty = true;
holdpedal = false;
patchTemp = &synth->mt32ram.patchTemp[partNum];
if (usePartNum == 8) {
// Nasty hack for rhythm
timbreTemp = NULL;
} else {
sprintf(name, "Part %d", partNum + 1);
timbreTemp = &synth->mt32ram.timbreTemp[partNum];
}
currentInstr[0] = 0;
currentInstr[10] = 0;
modulation = 0;
expression = 100;
pitchBend = 0;
activePartialCount = 0;
memset(patchCache, 0, sizeof(patchCache));
}
Part::~Part() {
while (!activePolys.isEmpty()) {
delete activePolys.takeFirst();
}
}
void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) {
if (nrpn) {
// The last RPN-related control change was for an NRPN,
// which the real synths don't support.
return;
}
if (rpn != 0) {
// The RPN has been set to something other than 0,
// which is the only RPN that these synths support
return;
}
patchTemp->patch.benderRange = midiDataEntryMSB > 24 ? 24 : midiDataEntryMSB;
updatePitchBenderRange();
}
void Part::setNRPN() {
nrpn = true;
}
void Part::setRPNLSB(unsigned char midiRPNLSB) {
nrpn = false;
rpn = (rpn & 0xFF00) | midiRPNLSB;
}
void Part::setRPNMSB(unsigned char midiRPNMSB) {
nrpn = false;
rpn = (rpn & 0x00FF) | (midiRPNMSB << 8);
}
void Part::setHoldPedal(bool pressed) {
if (holdpedal && !pressed) {
holdpedal = false;
stopPedalHold();
} else {
holdpedal = pressed;
}
}
Bit32s Part::getPitchBend() const {
return pitchBend;
}
void Part::setBend(unsigned int midiBend) {
// CONFIRMED:
pitchBend = ((signed(midiBend) - 8192) * pitchBenderRange) >> 14; // PORTABILITY NOTE: Assumes arithmetic shift
}
Bit8u Part::getModulation() const {
return modulation;
}
void Part::setModulation(unsigned int midiModulation) {
modulation = Bit8u(midiModulation);
}
void Part::resetAllControllers() {
modulation = 0;
expression = 100;
pitchBend = 0;
setHoldPedal(false);
}
void Part::reset() {
resetAllControllers();
allSoundOff();
rpn = 0xFFFF;
}
void RhythmPart::refresh() {
// (Re-)cache all the mapped timbres ahead of time
for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) {
int drumTimbreNum = rhythmTemp[drumNum].timbre;
if (drumTimbreNum >= 127) { // 94 on MT-32
continue;
}
PatchCache *cache = drumCache[drumNum];
backupCacheToPartials(cache);
for (int t = 0; t < 4; t++) {
// Common parameters, stored redundantly
cache[t].dirty = true;
cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
}
}
updatePitchBenderRange();
}
void Part::refresh() {
backupCacheToPartials(patchCache);
for (int t = 0; t < 4; t++) {
// Common parameters, stored redundantly
patchCache[t].dirty = true;
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
}
memcpy(currentInstr, timbreTemp->common.name, 10);
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, currentInstr);
updatePitchBenderRange();
}
const char *Part::getCurrentInstr() const {
return &currentInstr[0];
}
void RhythmPart::refreshTimbre(unsigned int absTimbreNum) {
for (int m = 0; m < 85; m++) {
if (rhythmTemp[m].timbre == absTimbreNum - 128) {
drumCache[m][0].dirty = true;
}
}
}
void Part::refreshTimbre(unsigned int absTimbreNum) {
if (getAbsTimbreNum() == absTimbreNum) {
memcpy(currentInstr, timbreTemp->common.name, 10);
patchCache[0].dirty = true;
}
}
void Part::setPatch(const PatchParam *patch) {
patchTemp->patch = *patch;
}
void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name);
}
void Part::setTimbre(TimbreParam *timbre) {
*timbreTemp = *timbre;
}
unsigned int RhythmPart::getAbsTimbreNum() const {
synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name);
return 0;
}
unsigned int Part::getAbsTimbreNum() const {
return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum;
}
#if MT32EMU_MONITOR_MIDI > 0
void RhythmPart::setProgram(unsigned int patchNum) {
synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
}
#else
void RhythmPart::setProgram(unsigned int) { }
#endif
void Part::setProgram(unsigned int patchNum) {
setPatch(&synth->mt32ram.patches[patchNum]);
holdpedal = false;
allSoundOff();
setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
refresh();
}
void Part::updatePitchBenderRange() {
pitchBenderRange = patchTemp->patch.benderRange * 683;
}
void Part::backupCacheToPartials(PatchCache cache[4]) {
// check if any partials are still playing with the old patch cache
// if so then duplicate the cached data from the part to the partial so that
// we can change the part's cache without affecting the partial.
// We delay this until now to avoid a copy operation with every note played
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->backupCacheToPartials(cache);
}
}
void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
backupCacheToPartials(cache);
int partialCount = 0;
for (int t = 0; t < 4; t++) {
if (((timbre->common.partialMute >> t) & 0x1) == 1) {
cache[t].playPartial = true;
partialCount++;
} else {
cache[t].playPartial = false;
continue;
}
// Calculate and cache common parameters
cache[t].srcPartial = timbre->partial[t];
cache[t].pcm = timbre->partial[t].wg.pcmWave;
switch (t) {
case 0:
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x2) ? true : false;
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
cache[t].structurePosition = 0;
cache[t].structurePair = 1;
break;
case 1:
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x1) ? true : false;
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
cache[t].structurePosition = 1;
cache[t].structurePair = 0;
break;
case 2:
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x2) ? true : false;
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
cache[t].structurePosition = 0;
cache[t].structurePair = 3;
break;
case 3:
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x1) ? true : false;
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
cache[t].structurePosition = 1;
cache[t].structurePair = 2;
break;
default:
break;
}
cache[t].partialParam = &timbre->partial[t];
cache[t].waveform = timbre->partial[t].wg.waveform;
}
for (int t = 0; t < 4; t++) {
// Common parameters, stored redundantly
cache[t].dirty = false;
cache[t].partialCount = partialCount;
cache[t].sustain = (timbre->common.noSustain == 0);
}
//synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform);
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): Recached timbre", name, currentInstr);
for (int i = 0; i < 4; i++) {
synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmWave, timbre->partial[i].wg.waveform);
}
#endif
}
const char *Part::getName() const {
return name;
}
void Part::setVolume(unsigned int midiVolume) {
// CONFIRMED: This calculation matches the table used in the control ROM
patchTemp->outputLevel = Bit8u(midiVolume * 100 / 127);
//synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
}
Bit8u Part::getVolume() const {
return patchTemp->outputLevel;
}
Bit8u Part::getExpression() const {
return expression;
}
void Part::setExpression(unsigned int midiExpression) {
// CONFIRMED: This calculation matches the table used in the control ROM
expression = Bit8u(midiExpression * 100 / 127);
}
void RhythmPart::setPan(unsigned int midiPan) {
// CONFIRMED: This does change patchTemp, but has no actual effect on playback.
#if MT32EMU_MONITOR_MIDI > 0
synth->printDebug("%s: Pointlessly setting pan (%d) on rhythm part", name, midiPan);
#endif
Part::setPan(midiPan);
}
void Part::setPan(unsigned int midiPan) {
// NOTE: Panning is inverted compared to GM.
// CM-32L: Divide by 8.5
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
// FIXME: MT-32: Divide by 9
//patchTemp->panpot = Bit8u(midiPan / 9);
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
}
/**
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
*/
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
int key = midiKey + patchTemp->patch.keyShift;
if (key < 36) {
// After keyShift is applied, key < 36, so move up by octaves
while (key < 36) {
key += 12;
}
} else if (key > 132) {
// After keyShift is applied, key > 132, so move down by octaves
while (key > 132) {
key -= 12;
}
}
key -= 24;
return key;
}
void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
if (midiKey < 24 || midiKey > 108) { /*> 87 on MT-32)*/
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
return;
}
unsigned int key = midiKey;
unsigned int drumNum = key - 24;
int drumTimbreNum = rhythmTemp[drumNum].timbre;
const int drumTimbreCount = 64 + synth->controlROMMap->timbreRCount; // 94 on MT-32, 128 on LAPC-I/CM32-L
if (drumTimbreNum == 127 || drumTimbreNum >= drumTimbreCount) { // timbre #127 is OFF, no sense to play it
synth->printDebug("%s: Attempted to play unmapped key %d (velocity %d)", name, midiKey, velocity);
return;
}
// CONFIRMED: Two special cases described by Mok
if (drumTimbreNum == 64 + 6) {
noteOff(0);
key = 1;
} else if (drumTimbreNum == 64 + 7) {
// This noteOff(0) is not performed on MT-32, only LAPC-I
noteOff(0);
key = 0;
}
int absTimbreNum = drumTimbreNum + 128;
TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
memcpy(currentInstr, timbre->common.name, 10);
if (drumCache[drumNum][0].dirty) {
cacheTimbre(drumCache[drumNum], timbre);
}
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): Start poly (drum %d, timbre %d): midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, drumNum, absTimbreNum, midiKey, key, velocity, modulation, expression, pitchBend);
#if MT32EMU_MONITOR_INSTRUMENTS > 1
// According to info from Mok, keyShift does not appear to affect anything on rhythm part on LAPC-I, but may do on MT-32 - needs investigation
synth->printDebug(" Patch: (timbreGroup %u), (timbreNum %u), (keyShift %u), fineTune %u, benderRange %u, assignMode %u, (reverbSwitch %u)", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
synth->printDebug(" PatchTemp: outputLevel %u, (panpot %u)", patchTemp->outputLevel, patchTemp->panpot);
synth->printDebug(" RhythmTemp: timbre %u, outputLevel %u, panpot %u, reverbSwitch %u", rhythmTemp[drumNum].timbre, rhythmTemp[drumNum].outputLevel, rhythmTemp[drumNum].panpot, rhythmTemp[drumNum].reverbSwitch);
#endif
#endif
playPoly(drumCache[drumNum], &rhythmTemp[drumNum], midiKey, key, velocity);
}
void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
unsigned int key = midiKeyToKey(midiKey);
if (patchCache[0].dirty) {
cacheTimbre(patchCache, timbreTemp);
}
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): Start poly: midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, midiKey, key, velocity, modulation, expression, pitchBend);
#if MT32EMU_MONITOR_INSTRUMENTS > 1
synth->printDebug(" Patch: timbreGroup %u, timbreNum %u, keyShift %u, fineTune %u, benderRange %u, assignMode %u, reverbSwitch %u", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
#endif
#endif
playPoly(patchCache, NULL, midiKey, key, velocity);
}
bool Part::abortFirstPoly(unsigned int key) {
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getKey() == key) {
return poly->startAbort();
}
}
return false;
}
bool Part::abortFirstPoly(PolyState polyState) {
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() == polyState) {
return poly->startAbort();
}
}
return false;
}
bool Part::abortFirstPolyPreferHeld() {
if (abortFirstPoly(POLY_Held)) {
return true;
}
return abortFirstPoly();
}
bool Part::abortFirstPoly() {
if (activePolys.isEmpty()) {
return false;
}
return activePolys.getFirst()->startAbort();
}
void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) {
// CONFIRMED: Even in single-assign mode, we don't abort playing polys if the timbre to play is completely muted.
unsigned int needPartials = cache[0].partialCount;
if (needPartials == 0) {
synth->printDebug("%s (%s): Completely muted instrument", name, currentInstr);
return;
}
if ((patchTemp->patch.assignMode & 2) == 0) {
// Single-assign mode
abortFirstPoly(key);
if (synth->isAbortingPoly()) return;
}
if (!synth->partialManager->freePartials(needPartials, partNum)) {
#if MT32EMU_MONITOR_PARTIALS > 0
synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode);
synth->printPartialUsage();
#endif
return;
}
if (synth->isAbortingPoly()) return;
Poly *poly = synth->partialManager->assignPolyToPart(this);
if (poly == NULL) {
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
return;
}
if (patchTemp->patch.assignMode & 1) {
// Priority to data first received
activePolys.prepend(poly);
} else {
activePolys.append(poly);
}
Partial *partials[4];
for (int x = 0; x < 4; x++) {
if (cache[x].playPartial) {
partials[x] = synth->partialManager->allocPartial(partNum);
activePartialCount++;
} else {
partials[x] = NULL;
}
}
poly->reset(key, velocity, cache[0].sustain, partials);
for (int x = 0; x < 4; x++) {
if (partials[x] != NULL) {
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
#endif
partials[x]->startPartial(this, poly, &cache[x], rhythmTemp, partials[cache[x].structurePair]);
}
}
#if MT32EMU_MONITOR_PARTIALS > 1
synth->printPartialUsage();
#endif
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
}
void Part::allNotesOff() {
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
// should treat the hold pedal as usual.
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
// if (poly->canSustain() || poly->getKey() == 0) {
// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
if (poly->canSustain()) {
poly->noteOff(holdpedal);
}
}
}
void Part::allSoundOff() {
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
// we're only using this method internally.
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->startDecay();
}
}
void Part::stopPedalHold() {
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
poly->stopPedalHold();
}
}
void RhythmPart::noteOff(unsigned int midiKey) {
stopNote(midiKey);
}
void Part::noteOff(unsigned int midiKey) {
stopNote(midiKeyToKey(midiKey));
}
void Part::stopNote(unsigned int key) {
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
#endif
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
if (poly->noteOff(holdpedal && key != 0)) {
break;
}
}
}
}
const MemParams::PatchTemp *Part::getPatchTemp() const {
return patchTemp;
}
unsigned int Part::getActivePartialCount() const {
return activePartialCount;
}
const Poly *Part::getFirstActivePoly() const {
return activePolys.getFirst();
}
unsigned int Part::getActiveNonReleasingPartialCount() const {
unsigned int activeNonReleasingPartialCount = 0;
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
if (poly->getState() != POLY_Releasing) {
activeNonReleasingPartialCount += poly->getActivePartialCount();
}
}
return activeNonReleasingPartialCount;
}
Synth *Part::getSynth() const {
return synth;
}
void Part::partialDeactivated(Poly *poly) {
activePartialCount--;
if (!poly->isActive()) {
activePolys.remove(poly);
synth->partialManager->polyFreed(poly);
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
}
}
PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
bool PolyList::isEmpty() const {
#ifdef MT32EMU_POLY_LIST_DEBUG
if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
}
#endif
return firstPoly == NULL && lastPoly == NULL;
}
Poly *PolyList::getFirst() const {
return firstPoly;
}
Poly *PolyList::getLast() const {
return lastPoly;
}
void PolyList::prepend(Poly *poly) {
#ifdef MT32EMU_POLY_LIST_DEBUG
if (poly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
}
#endif
poly->setNext(firstPoly);
firstPoly = poly;
if (lastPoly == NULL) {
lastPoly = poly;
}
}
void PolyList::append(Poly *poly) {
#ifdef MT32EMU_POLY_LIST_DEBUG
if (poly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
}
#endif
poly->setNext(NULL);
if (lastPoly != NULL) {
#ifdef MT32EMU_POLY_LIST_DEBUG
if (lastPoly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in the lastPoly\n");
}
#endif
lastPoly->setNext(poly);
}
lastPoly = poly;
if (firstPoly == NULL) {
firstPoly = poly;
}
}
Poly *PolyList::takeFirst() {
Poly *oldFirst = firstPoly;
firstPoly = oldFirst->getNext();
if (firstPoly == NULL) {
#ifdef MT32EMU_POLY_LIST_DEBUG
if (lastPoly != oldFirst) {
printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
}
#endif
lastPoly = NULL;
}
oldFirst->setNext(NULL);
return oldFirst;
}
void PolyList::remove(Poly * const polyToRemove) {
if (polyToRemove == firstPoly) {
takeFirst();
return;
}
for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
if (poly->getNext() == polyToRemove) {
if (polyToRemove == lastPoly) {
#ifdef MT32EMU_POLY_LIST_DEBUG
if (lastPoly->getNext() != NULL) {
printf("PolyList: Non-NULL next field in the lastPoly\n");
}
#endif
lastPoly = poly;
}
poly->setNext(polyToRemove->getNext());
polyToRemove->setNext(NULL);
break;
}
}
}
} // namespace MT32Emu

153
src/SOUND/munt/Part.h Normal file
View File

@@ -0,0 +1,153 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PART_H
#define MT32EMU_PART_H
#include "globals.h"
#include "internals.h"
#include "Types.h"
#include "Structures.h"
namespace MT32Emu {
class Poly;
class Synth;
class PolyList {
private:
Poly *firstPoly;
Poly *lastPoly;
public:
PolyList();
bool isEmpty() const;
Poly *getFirst() const;
Poly *getLast() const;
void prepend(Poly *poly);
void append(Poly *poly);
Poly *takeFirst();
void remove(Poly * const poly);
};
class Part {
private:
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
TimbreParam *timbreTemp;
// 0=Part 1, .. 7=Part 8, 8=Rhythm
unsigned int partNum;
bool holdpedal;
unsigned int activePartialCount;
PatchCache patchCache[4];
PolyList activePolys;
void setPatch(const PatchParam *patch);
unsigned int midiKeyToKey(unsigned int midiKey);
bool abortFirstPoly(unsigned int key);
protected:
Synth *synth;
// Direct pointer into sysex-addressable memory
MemParams::PatchTemp *patchTemp;
char name[8]; // "Part 1".."Part 8", "Rhythm"
char currentInstr[11];
Bit8u modulation;
Bit8u expression;
Bit32s pitchBend;
bool nrpn;
Bit16u rpn;
Bit16u pitchBenderRange; // (patchTemp->patch.benderRange * 683) at the time of the last MIDI program change or MIDI data entry.
void backupCacheToPartials(PatchCache cache[4]);
void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre);
void playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity);
void stopNote(unsigned int key);
const char *getName() const;
public:
Part(Synth *synth, unsigned int usePartNum);
virtual ~Part();
void reset();
void setDataEntryMSB(unsigned char midiDataEntryMSB);
void setNRPN();
void setRPNLSB(unsigned char midiRPNLSB);
void setRPNMSB(unsigned char midiRPNMSB);
void resetAllControllers();
virtual void noteOn(unsigned int midiKey, unsigned int velocity);
virtual void noteOff(unsigned int midiKey);
void allNotesOff();
void allSoundOff();
Bit8u getVolume() const; // Internal volume, 0-100, exposed for use by ExternalInterface
void setVolume(unsigned int midiVolume);
Bit8u getModulation() const;
void setModulation(unsigned int midiModulation);
Bit8u getExpression() const;
void setExpression(unsigned int midiExpression);
virtual void setPan(unsigned int midiPan);
Bit32s getPitchBend() const;
void setBend(unsigned int midiBend);
virtual void setProgram(unsigned int midiProgram);
void setHoldPedal(bool pedalval);
void stopPedalHold();
void updatePitchBenderRange();
virtual void refresh();
virtual void refreshTimbre(unsigned int absTimbreNum);
virtual void setTimbre(TimbreParam *timbre);
virtual unsigned int getAbsTimbreNum() const;
const char *getCurrentInstr() const;
const Poly *getFirstActivePoly() const;
unsigned int getActivePartialCount() const;
unsigned int getActiveNonReleasingPartialCount() const;
Synth *getSynth() const;
const MemParams::PatchTemp *getPatchTemp() const;
// This should only be called by Poly
void partialDeactivated(Poly *poly);
// These are rather specialised, and should probably only be used by PartialManager
bool abortFirstPoly(PolyState polyState);
// Abort the first poly in PolyState_HELD, or if none exists, the first active poly in any state.
bool abortFirstPolyPreferHeld();
bool abortFirstPoly();
}; // class Part
class RhythmPart: public Part {
// Pointer to the area of the MT-32's memory dedicated to rhythm
const MemParams::RhythmTemp *rhythmTemp;
// This caches the timbres/settings in use by the rhythm part
PatchCache drumCache[85][4];
public:
RhythmPart(Synth *synth, unsigned int usePartNum);
void refresh();
void refreshTimbre(unsigned int timbreNum);
void setTimbre(TimbreParam *timbre);
void noteOn(unsigned int key, unsigned int velocity);
void noteOff(unsigned int midiKey);
unsigned int getAbsTimbreNum() const;
void setPan(unsigned int midiPan);
void setProgram(unsigned int patchNum);
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_PART_H

403
src/SOUND/munt/Partial.cpp Normal file
View File

@@ -0,0 +1,403 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "internals.h"
#include "Partial.h"
#include "Part.h"
#include "Poly.h"
#include "Synth.h"
#include "Tables.h"
#include "TVA.h"
#include "TVF.h"
#include "TVP.h"
namespace MT32Emu {
static const Bit8u PAN_NUMERATOR_MASTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7};
static const Bit8u PAN_NUMERATOR_SLAVE[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7};
// We assume the pan is applied using the same 13-bit multiplier circuit that is also used for ring modulation
// because of the observed sample overflow, so the panSetting values are likely mapped in a similar way via a LUT.
// FIXME: Sample analysis suggests that the use of panSetting is linear, but there are some quirks that still need to be resolved.
static Bit32s getPANFactor(Bit32s panSetting) {
static const Bit32s PAN_FACTORS_COUNT = 15;
static Bit32s PAN_FACTORS[PAN_FACTORS_COUNT];
static bool firstRun = true;
if (firstRun) {
firstRun = false;
for (Bit32u i = 1; i < PAN_FACTORS_COUNT; i++) {
PAN_FACTORS[i] = Bit32s(0.5 + i * 8192.0 / double(PAN_FACTORS_COUNT - 1));
}
}
return PAN_FACTORS[panSetting];
}
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0),
floatMode(useSynth->getSelectedRendererType() == RendererType_FLOAT) {
// Initialisation of tva, tvp and tvf uses 'this' pointer
// and thus should not be in the initializer list to avoid a compiler warning
tva = new TVA(this, &ampRamp);
tvp = new TVP(this);
tvf = new TVF(this, &cutoffModifierRamp);
ownerPart = -1;
poly = NULL;
pair = NULL;
switch (synth->getSelectedRendererType()) {
case RendererType_BIT16S:
la32Pair = new LA32IntPartialPair;
break;
case RendererType_FLOAT:
la32Pair = new LA32FloatPartialPair;
break;
default:
la32Pair = NULL;
}
}
Partial::~Partial() {
delete la32Pair;
delete tva;
delete tvp;
delete tvf;
}
// Only used for debugging purposes
int Partial::debugGetPartialNum() const {
return debugPartialNum;
}
// Only used for debugging purposes
Bit32u Partial::debugGetSampleNum() const {
return sampleNum;
}
int Partial::getOwnerPart() const {
return ownerPart;
}
bool Partial::isActive() const {
return ownerPart > -1;
}
const Poly *Partial::getPoly() const {
return poly;
}
void Partial::activate(int part) {
// This just marks the partial as being assigned to a part
ownerPart = part;
}
void Partial::deactivate() {
if (!isActive()) {
return;
}
ownerPart = -1;
if (poly != NULL) {
poly->partialDeactivated(this);
}
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
synth->printPartialUsage(sampleNum);
#endif
if (isRingModulatingSlave()) {
pair->la32Pair->deactivate(LA32PartialPair::SLAVE);
} else {
la32Pair->deactivate(LA32PartialPair::MASTER);
if (hasRingModulatingSlave()) {
pair->deactivate();
pair = NULL;
}
}
if (pair != NULL) {
pair->pair = NULL;
}
}
void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *usePatchCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial) {
if (usePoly == NULL || usePatchCache == NULL) {
synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", debugPartialNum, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK");
return;
}
patchCache = usePatchCache;
poly = usePoly;
mixType = patchCache->structureMix;
structurePosition = patchCache->structurePosition;
Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot;
if (mixType == 3) {
if (structurePosition == 0) {
panSetting = PAN_NUMERATOR_MASTER[panSetting] << 1;
} else {
panSetting = PAN_NUMERATOR_SLAVE[panSetting] << 1;
}
// Do a normal mix independent of any pair partial.
mixType = 0;
pairPartial = NULL;
} else {
// Mok wanted an option for smoother panning, and we love Mok.
#ifndef INACCURATE_SMOOTH_PAN
// CONFIRMED by Mok: exactly bytes like this (right shifted?) are sent to the LA32.
panSetting &= 0x0E;
#endif
}
leftPanValue = synth->reversedStereoEnabled ? 14 - panSetting : panSetting;
rightPanValue = 14 - leftPanValue;
if (!floatMode) {
leftPanValue = getPANFactor(leftPanValue);
rightPanValue = getPANFactor(rightPanValue);
}
// SEMI-CONFIRMED: From sample analysis:
// Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways.
// Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated.
// It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens.
// Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close.
// In this case that timbre can sound totally different depending of the way it is mixed up.
// Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4).
// Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently.
// Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one.
// The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal
// whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently.
// This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02.
// For my personal taste, this behaviour rather enriches the sounding and should be emulated.
// Also, the current partial allocator model probably needs to be refined.
if (debugPartialNum & 8) {
leftPanValue = -leftPanValue;
rightPanValue = -rightPanValue;
}
if (patchCache->PCMPartial) {
pcmNum = patchCache->pcm;
if (synth->controlROMMap->pcmCount > 128) {
// CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter.
if (patchCache->waveform > 1) {
pcmNum += 128;
}
}
pcmWave = &synth->pcmWaves[pcmNum];
} else {
pcmWave = NULL;
}
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
if (pulseWidthVal < 0) {
pulseWidthVal = 0;
} else if (pulseWidthVal > 255) {
pulseWidthVal = 255;
}
pair = pairPartial;
alreadyOutputed = false;
tva->reset(part, patchCache->partialParam, rhythmTemp);
tvp->reset(part, patchCache->partialParam);
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
LA32PartialPair::PairType pairType;
LA32PartialPair *useLA32Pair;
if (isRingModulatingSlave()) {
pairType = LA32PartialPair::SLAVE;
useLA32Pair = pair->la32Pair;
} else {
pairType = LA32PartialPair::MASTER;
la32Pair->init(hasRingModulatingSlave(), mixType == 1);
useLA32Pair = la32Pair;
}
if (isPCM()) {
useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop);
} else {
useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1);
}
if (!hasRingModulatingSlave()) {
la32Pair->deactivate(LA32PartialPair::SLAVE);
}
}
Bit32u Partial::getAmpValue() {
// SEMI-CONFIRMED: From sample analysis:
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
// This gives results within +/- 2 at the output (before any DAC bitshifting)
// when sustaining at levels 156 - 255 with no modifiers.
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
// positive amps, so negative still needs to be explored, as well as lower levels.
//
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
// TODO: The tests above were performed using the float model, to be refined
Bit32u ampRampVal = 67117056 - ampRamp.nextValue();
if (ampRamp.checkInterrupt()) {
tva->handleInterrupt();
}
return ampRampVal;
}
Bit32u Partial::getCutoffValue() {
if (isPCM()) {
return 0;
}
Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
if (cutoffModifierRamp.checkInterrupt()) {
tvf->handleInterrupt();
}
return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal;
}
bool Partial::hasRingModulatingSlave() const {
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
}
bool Partial::isRingModulatingSlave() const {
return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
}
bool Partial::isPCM() const {
return pcmWave != NULL;
}
const ControlROMPCMStruct *Partial::getControlROMPCMStruct() const {
if (pcmWave != NULL) {
return pcmWave->controlROMPCMStruct;
}
return NULL;
}
Synth *Partial::getSynth() const {
return synth;
}
TVA *Partial::getTVA() const {
return tva;
}
void Partial::backupCache(const PatchCache &cache) {
if (patchCache == &cache) {
cachebackup = cache;
patchCache = &cachebackup;
}
}
bool Partial::canProduceOutput() {
if (!isActive() || alreadyOutputed || isRingModulatingSlave()) {
return false;
}
if (poly == NULL) {
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
return false;
}
return true;
}
template <class LA32PairImpl>
bool Partial::generateNextSample(LA32PairImpl *la32PairImpl) {
if (!tva->isPlaying() || !la32PairImpl->isActive(LA32PartialPair::MASTER)) {
deactivate();
return false;
}
la32PairImpl->generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue());
if (hasRingModulatingSlave()) {
la32PairImpl->generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue());
if (!pair->tva->isPlaying() || !la32PairImpl->isActive(LA32PartialPair::SLAVE)) {
pair->deactivate();
if (mixType == 2) {
deactivate();
return false;
}
}
}
return true;
}
void Partial::produceAndMixSample(IntSample *&leftBuf, IntSample *&rightBuf, LA32IntPartialPair *la32IntPair) {
IntSampleEx sample = la32IntPair->nextOutSample();
// FIXME: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191
// when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation,
// and it seems to be caused by limited precision of the common multiplication circuit.
// From analysis of this overflow, it is obvious that the right channel output is actually found
// by subtraction of the left channel output from the input.
// Though, it is unknown whether this overflow is exploited somewhere.
IntSampleEx leftOut = ((sample * leftPanValue) >> 13) + IntSampleEx(*leftBuf);
IntSampleEx rightOut = ((sample * rightPanValue) >> 13) + IntSampleEx(*rightBuf);
*(leftBuf++) = Synth::clipSampleEx(leftOut);
*(rightBuf++) = Synth::clipSampleEx(rightOut);
}
void Partial::produceAndMixSample(FloatSample *&leftBuf, FloatSample *&rightBuf, LA32FloatPartialPair *la32FloatPair) {
FloatSample sample = la32FloatPair->nextOutSample();
FloatSample leftOut = (sample * leftPanValue) / 14.0f;
FloatSample rightOut = (sample * rightPanValue) / 14.0f;
*(leftBuf++) += leftOut;
*(rightBuf++) += rightOut;
}
template <class Sample, class LA32PairImpl>
bool Partial::doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length, LA32PairImpl *la32PairImpl) {
if (!canProduceOutput()) return false;
alreadyOutputed = true;
for (sampleNum = 0; sampleNum < length; sampleNum++) {
if (!generateNextSample(la32PairImpl)) break;
produceAndMixSample(leftBuf, rightBuf, la32PairImpl);
}
sampleNum = 0;
return true;
}
bool Partial::produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length) {
if (floatMode) {
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
return false;
}
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32IntPartialPair *>(la32Pair));
}
bool Partial::produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length) {
if (!floatMode) {
synth->printDebug("Partial: Invalid call to produceOutput()!\n", synth->getSelectedRendererType());
return false;
}
return doProduceOutput(leftBuf, rightBuf, length, static_cast<LA32FloatPartialPair *>(la32Pair));
}
bool Partial::shouldReverb() {
if (!isActive()) {
return false;
}
return patchCache->reverb;
}
void Partial::startAbort() {
// This is called when the partial manager needs to terminate partials for re-use by a new Poly.
tva->startAbort();
}
void Partial::startDecayAll() {
tva->startDecay();
tvp->startDecay();
tvf->startDecay();
}
} // namespace MT32Emu

129
src/SOUND/munt/Partial.h Normal file
View File

@@ -0,0 +1,129 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PARTIAL_H
#define MT32EMU_PARTIAL_H
#include "globals.h"
#include "internals.h"
#include "Types.h"
#include "Structures.h"
#include "LA32Ramp.h"
#include "LA32WaveGenerator.h"
#include "LA32FloatWaveGenerator.h"
namespace MT32Emu {
class Part;
class Poly;
class Synth;
class TVA;
class TVF;
class TVP;
struct ControlROMPCMStruct;
// A partial represents one of up to four waveform generators currently playing within a poly.
class Partial {
private:
Synth *synth;
const int debugPartialNum; // Only used for debugging
// Number of the sample currently being rendered by produceOutput(), or 0 if no run is in progress
// This is only kept available for debugging purposes.
Bit32u sampleNum;
// Actually, this is a 4-bit register but we abuse this to emulate inverted mixing.
// Also we double the value to enable INACCURATE_SMOOTH_PAN, with respect to MoK.
Bit32s leftPanValue, rightPanValue;
int ownerPart; // -1 if unassigned
int mixType;
int structurePosition; // 0 or 1 of a structure pair
// Only used for PCM partials
int pcmNum;
// FIXME: Give this a better name (e.g. pcmWaveInfo)
PCMWaveEntry *pcmWave;
// Final pulse width value, with velfollow applied, matching what is sent to the LA32.
// Range: 0-255
int pulseWidthVal;
Poly *poly;
Partial *pair;
TVA *tva;
TVP *tvp;
TVF *tvf;
LA32Ramp ampRamp;
LA32Ramp cutoffModifierRamp;
// TODO: This should be owned by PartialPair
LA32PartialPair *la32Pair;
const bool floatMode;
const PatchCache *patchCache;
PatchCache cachebackup;
Bit32u getAmpValue();
Bit32u getCutoffValue();
template <class Sample, class LA32PairImpl>
bool doProduceOutput(Sample *leftBuf, Sample *rightBuf, Bit32u length, LA32PairImpl *la32PairImpl);
bool canProduceOutput();
template <class LA32PairImpl>
bool generateNextSample(LA32PairImpl *la32PairImpl);
void produceAndMixSample(IntSample *&leftBuf, IntSample *&rightBuf, LA32IntPartialPair *la32IntPair);
void produceAndMixSample(FloatSample *&leftBuf, FloatSample *&rightBuf, LA32FloatPartialPair *la32FloatPair);
public:
bool alreadyOutputed;
Partial(Synth *synth, int debugPartialNum);
~Partial();
int debugGetPartialNum() const;
Bit32u debugGetSampleNum() const;
int getOwnerPart() const;
const Poly *getPoly() const;
bool isActive() const;
void activate(int part);
void deactivate(void);
void startPartial(const Part *part, Poly *usePoly, const PatchCache *useCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial);
void startAbort();
void startDecayAll();
bool shouldReverb();
bool hasRingModulatingSlave() const;
bool isRingModulatingSlave() const;
bool isPCM() const;
const ControlROMPCMStruct *getControlROMPCMStruct() const;
Synth *getSynth() const;
TVA *getTVA() const;
void backupCache(const PatchCache &cache);
// Returns true only if data written to buffer
// These functions produce processed stereo samples
// made from combining this single partial with its pair, if it has one.
bool produceOutput(IntSample *leftBuf, IntSample *rightBuf, Bit32u length);
bool produceOutput(FloatSample *leftBuf, FloatSample *rightBuf, Bit32u length);
}; // class Partial
} // namespace MT32Emu
#endif // #ifndef MT32EMU_PARTIAL_H

View File

@@ -0,0 +1,298 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include <cstring>
#include "internals.h"
#include "PartialManager.h"
#include "Part.h"
#include "Partial.h"
#include "Poly.h"
#include "Synth.h"
namespace MT32Emu {
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
synth = useSynth;
parts = useParts;
partialTable = new Partial *[synth->getPartialCount()];
freePolys = new Poly *[synth->getPartialCount()];
firstFreePolyIndex = 0;
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
partialTable[i] = new Partial(synth, i);
freePolys[i] = new Poly();
}
}
PartialManager::~PartialManager(void) {
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
delete partialTable[i];
if (freePolys[i] != NULL) delete freePolys[i];
}
delete[] partialTable;
delete[] freePolys;
}
void PartialManager::clearAlreadyOutputed() {
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
partialTable[i]->alreadyOutputed = false;
}
}
bool PartialManager::shouldReverb(int i) {
return partialTable[i]->shouldReverb();
}
bool PartialManager::produceOutput(int i, IntSample *leftBuf, IntSample *rightBuf, Bit32u bufferLength) {
return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength);
}
bool PartialManager::produceOutput(int i, FloatSample *leftBuf, FloatSample *rightBuf, Bit32u bufferLength) {
return partialTable[i]->produceOutput(leftBuf, rightBuf, bufferLength);
}
void PartialManager::deactivateAll() {
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
partialTable[i]->deactivate();
}
}
unsigned int PartialManager::setReserve(Bit8u *rset) {
unsigned int pr = 0;
for (int x = 0; x <= 8; x++) {
numReservedPartialsForPart[x] = rset[x];
pr += rset[x];
}
return pr;
}
Partial *PartialManager::allocPartial(int partNum) {
Partial *outPartial = NULL;
// Get the first inactive partial
for (unsigned int partialNum = 0; partialNum < synth->getPartialCount(); partialNum++) {
if (!partialTable[partialNum]->isActive()) {
outPartial = partialTable[partialNum];
break;
}
}
if (outPartial != NULL) {
outPartial->activate(partNum);
}
return outPartial;
}
unsigned int PartialManager::getFreePartialCount(void) {
int count = 0;
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
if (!partialTable[i]->isActive()) {
count++;
}
}
return count;
}
// This function is solely used to gather data for debug output at the moment.
void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) {
memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int));
for (unsigned int i = 0; i < synth->getPartialCount(); i++) {
if (partialTable[i]->isActive()) {
perPartPartialUsage[partialTable[i]->getOwnerPart()]++;
}
}
}
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly
// in POLY_Releasing, then kills its first releasing poly.
// Parts with higher priority than minPart are not checked.
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
bool PartialManager::abortFirstReleasingPolyWhereReserveExceeded(int minPart) {
if (minPart == 8) {
// Rhythm is highest priority
minPart = -1;
}
for (int partNum = 7; partNum >= minPart; partNum--) {
int usePartNum = partNum == -1 ? 8 : partNum;
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
// This part has exceeded its reserved partial count.
// If it has any releasing polys, kill its first one and we're done.
if (parts[usePartNum]->abortFirstPoly(POLY_Releasing)) {
return true;
}
}
}
return false;
}
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly, then kills
// its first poly in POLY_Held - or failing that, its first poly in any state.
// Parts with higher priority than minPart are not checked.
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
bool PartialManager::abortFirstPolyPreferHeldWhereReserveExceeded(int minPart) {
if (minPart == 8) {
// Rhythm is highest priority
minPart = -1;
}
for (int partNum = 7; partNum >= minPart; partNum--) {
int usePartNum = partNum == -1 ? 8 : partNum;
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
// This part has exceeded its reserved partial count.
// If it has any polys, kill its first (preferably held) one and we're done.
if (parts[usePartNum]->abortFirstPolyPreferHeld()) {
return true;
}
}
}
return false;
}
bool PartialManager::freePartials(unsigned int needed, int partNum) {
// CONFIRMED: Barring bugs, this matches the real LAPC-I according to information from Mok.
// BUG: There's a bug in the LAPC-I implementation:
// When allocating for rhythm part, or when allocating for a part that is using fewer partials than it has reserved,
// held and playing polys on the rhythm part can potentially be aborted before releasing polys on the rhythm part.
// This bug isn't present on MT-32.
// I consider this to be a bug because I think that playing polys should always have priority over held polys,
// and held polys should always have priority over releasing polys.
// NOTE: This code generally aborts polys in parts (according to certain conditions) in the following order:
// 7, 6, 5, 4, 3, 2, 1, 0, 8 (rhythm)
// (from lowest priority, meaning most likely to have polys aborted, to highest priority, meaning least likely)
if (needed == 0) {
return true;
}
// Note that calling getFreePartialCount() also ensures that numReservedPartialsPerPart is up-to-date
if (getFreePartialCount() >= needed) {
return true;
}
// Note: These #ifdefs are temporary until we have proper "quirk" configuration.
// Also, the MT-32 version isn't properly confirmed yet.
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
// On MT-32, we bail out before even killing releasing partials if the allocating part has exceeded its reserve and is configured for priority-to-earlier-polys.
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum] && (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1)) {
return false;
}
#endif
for (;;) {
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
// Abort releasing polys in parts that have exceeded their partial reservation (working backwards from part 7, with rhythm last)
if (!abortFirstReleasingPolyWhereReserveExceeded(-1)) {
break;
}
#else
// Abort releasing polys in non-rhythm parts that have exceeded their partial reservation (working backwards from part 7)
if (!abortFirstReleasingPolyWhereReserveExceeded(0)) {
break;
}
#endif
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
return true;
}
}
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum]) {
// With the new partials we're freeing for, we would end up using more partials than we have reserved.
if (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1) {
// Priority is given to earlier polys, so just give up
return false;
}
// Only abort held polys in the target part and parts that have a lower priority
// (higher part number = lower priority, except for rhythm, which has the highest priority).
for (;;) {
if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) {
break;
}
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
return true;
}
}
if (needed > numReservedPartialsForPart[partNum]) {
return false;
}
} else {
// At this point, we're certain that we've reserved enough partials to play our poly.
// Check all parts from lowest to highest priority to see whether they've exceeded their
// reserve, and abort their polys until until we have enough free partials or they're within
// their reserve allocation.
for (;;) {
if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) {
break;
}
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
return true;
}
}
}
// Abort polys in the target part until there are enough free partials for the new one
for (;;) {
if (!parts[partNum]->abortFirstPolyPreferHeld()) {
break;
}
if (synth->isAbortingPoly() || getFreePartialCount() >= needed) {
return true;
}
}
// Aww, not enough partials for you.
return false;
}
const Partial *PartialManager::getPartial(unsigned int partialNum) const {
if (partialNum > synth->getPartialCount() - 1) {
return NULL;
}
return partialTable[partialNum];
}
Poly *PartialManager::assignPolyToPart(Part *part) {
if (firstFreePolyIndex < synth->getPartialCount()) {
Poly *poly = freePolys[firstFreePolyIndex];
freePolys[firstFreePolyIndex] = NULL;
firstFreePolyIndex++;
poly->setPart(part);
return poly;
}
return NULL;
}
void PartialManager::polyFreed(Poly *poly) {
if (0 == firstFreePolyIndex) {
synth->printDebug("Cannot return freed poly, currently active polys:\n");
for (Bit32u partNum = 0; partNum < 9; partNum++) {
const Poly *activePoly = synth->getPart(partNum)->getFirstActivePoly();
Bit32u polyCount = 0;
while (activePoly != NULL) {
activePoly = activePoly->getNext();
polyCount++;
}
synth->printDebug("Part: %i, active poly count: %i\n", partNum, polyCount);
}
}
poly->setPart(NULL);
firstFreePolyIndex--;
freePolys[firstFreePolyIndex] = poly;
}
} // namespace MT32Emu

View File

@@ -0,0 +1,64 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PARTIALMANAGER_H
#define MT32EMU_PARTIALMANAGER_H
#include "globals.h"
#include "internals.h"
#include "Types.h"
namespace MT32Emu {
class Part;
class Partial;
class Poly;
class Synth;
class PartialManager {
private:
Synth *synth;
Part **parts;
Poly **freePolys;
Partial **partialTable;
Bit8u numReservedPartialsForPart[9];
Bit32u firstFreePolyIndex;
bool abortFirstReleasingPolyWhereReserveExceeded(int minPart);
bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart);
public:
PartialManager(Synth *synth, Part **parts);
~PartialManager();
Partial *allocPartial(int partNum);
unsigned int getFreePartialCount(void);
void getPerPartPartialUsage(unsigned int perPartPartialUsage[9]);
bool freePartials(unsigned int needed, int partNum);
unsigned int setReserve(Bit8u *rset);
void deactivateAll();
bool produceOutput(int i, IntSample *leftBuf, IntSample *rightBuf, Bit32u bufferLength);
bool produceOutput(int i, FloatSample *leftBuf, FloatSample *rightBuf, Bit32u bufferLength);
bool shouldReverb(int i);
void clearAlreadyOutputed();
const Partial *getPartial(unsigned int partialNum) const;
Poly *assignPolyToPart(Part *part);
void polyFreed(Poly *poly);
}; // class PartialManager
} // namespace MT32Emu
#endif // #ifndef MT32EMU_PARTIALMANAGER_H

190
src/SOUND/munt/Poly.cpp Normal file
View File

@@ -0,0 +1,190 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "internals.h"
#include "Poly.h"
#include "Part.h"
#include "Partial.h"
#include "Synth.h"
namespace MT32Emu {
Poly::Poly() {
part = NULL;
key = 255;
velocity = 255;
sustain = false;
activePartialCount = 0;
for (int i = 0; i < 4; i++) {
partials[i] = NULL;
}
state = POLY_Inactive;
next = NULL;
}
void Poly::setPart(Part *usePart) {
part = usePart;
}
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
if (isActive()) {
// This should never happen
part->getSynth()->printDebug("Resetting active poly. Active partial count: %i\n", activePartialCount);
for (int i = 0; i < 4; i++) {
if (partials[i] != NULL && partials[i]->isActive()) {
partials[i]->deactivate();
activePartialCount--;
}
}
state = POLY_Inactive;
}
key = newKey;
velocity = newVelocity;
sustain = newSustain;
activePartialCount = 0;
for (int i = 0; i < 4; i++) {
partials[i] = newPartials[i];
if (newPartials[i] != NULL) {
activePartialCount++;
state = POLY_Playing;
}
}
}
bool Poly::noteOff(bool pedalHeld) {
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
if (state == POLY_Inactive || state == POLY_Releasing) {
return false;
}
if (pedalHeld) {
if (state == POLY_Held) {
return false;
}
state = POLY_Held;
} else {
startDecay();
}
return true;
}
bool Poly::stopPedalHold() {
if (state != POLY_Held) {
return false;
}
return startDecay();
}
bool Poly::startDecay() {
if (state == POLY_Inactive || state == POLY_Releasing) {
return false;
}
state = POLY_Releasing;
for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
if (partial != NULL) {
partial->startDecayAll();
}
}
return true;
}
bool Poly::startAbort() {
if (state == POLY_Inactive || part->getSynth()->isAbortingPoly()) {
return false;
}
for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
if (partial != NULL) {
partial->startAbort();
part->getSynth()->abortingPoly = this;
}
}
return true;
}
void Poly::backupCacheToPartials(PatchCache cache[4]) {
for (int partialNum = 0; partialNum < 4; partialNum++) {
Partial *partial = partials[partialNum];
if (partial != NULL) {
partial->backupCache(cache[partialNum]);
}
}
}
/**
* Returns the internal key identifier.
* For non-rhythm, this is within the range 12 to 108.
* For rhythm on MT-32, this is 0 or 1 (special cases) or within the range 24 to 87.
* For rhythm on devices with extended PCM sounds (e.g. CM-32L), this is 0, 1 or 24 to 108
*/
unsigned int Poly::getKey() const {
return key;
}
unsigned int Poly::getVelocity() const {
return velocity;
}
bool Poly::canSustain() const {
return sustain;
}
PolyState Poly::getState() const {
return state;
}
unsigned int Poly::getActivePartialCount() const {
return activePartialCount;
}
bool Poly::isActive() const {
return state != POLY_Inactive;
}
// This is called by Partial to inform the poly that the Partial has deactivated
void Poly::partialDeactivated(Partial *partial) {
for (int i = 0; i < 4; i++) {
if (partials[i] == partial) {
partials[i] = NULL;
activePartialCount--;
}
}
if (activePartialCount == 0) {
state = POLY_Inactive;
if (part->getSynth()->abortingPoly == this) {
part->getSynth()->abortingPoly = NULL;
}
}
part->partialDeactivated(this);
}
Poly *Poly::getNext() const {
return next;
}
void Poly::setNext(Poly *poly) {
next = poly;
}
} // namespace MT32Emu

70
src/SOUND/munt/Poly.h Normal file
View File

@@ -0,0 +1,70 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_POLY_H
#define MT32EMU_POLY_H
#include "globals.h"
#include "internals.h"
namespace MT32Emu {
class Part;
class Partial;
struct PatchCache;
class Poly {
private:
Part *part;
unsigned int key;
unsigned int velocity;
unsigned int activePartialCount;
bool sustain;
PolyState state;
Partial *partials[4];
Poly *next;
public:
Poly();
void setPart(Part *usePart);
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
bool noteOff(bool pedalHeld);
bool stopPedalHold();
bool startDecay();
bool startAbort();
void backupCacheToPartials(PatchCache cache[4]);
unsigned int getKey() const;
unsigned int getVelocity() const;
bool canSustain() const;
PolyState getState() const;
unsigned int getActivePartialCount() const;
bool isActive() const;
void partialDeactivated(Partial *partial);
Poly *getNext() const;
void setNext(Poly *poly);
}; // class Poly
} // namespace MT32Emu
#endif // #ifndef MT32EMU_POLY_H

117
src/SOUND/munt/ROMInfo.cpp Normal file
View File

@@ -0,0 +1,117 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include "internals.h"
#include "ROMInfo.h"
namespace MT32Emu {
static const ROMInfo *getKnownROMInfoFromList(Bit32u index) {
// Known ROMs
static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL};
static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL};
static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL};
static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL};
static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL};
static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL};
static const ROMInfo * const ROM_INFOS[] = {
&CTRL_MT32_V1_04,
&CTRL_MT32_V1_05,
&CTRL_MT32_V1_06,
&CTRL_MT32_V1_07,
&CTRL_MT32_BLUER,
&CTRL_CM32L_V1_00,
&CTRL_CM32L_V1_02,
&PCM_MT32,
&PCM_CM32L,
NULL};
return ROM_INFOS[index];
}
const ROMInfo* ROMInfo::getROMInfo(File *file) {
size_t fileSize = file->getSize();
for (Bit32u i = 0; getKnownROMInfoFromList(i) != NULL; i++) {
const ROMInfo *romInfo = getKnownROMInfoFromList(i);
if (fileSize == romInfo->fileSize && !strcmp(file->getSHA1(), romInfo->sha1Digest)) {
return romInfo;
}
}
return NULL;
}
void ROMInfo::freeROMInfo(const ROMInfo *romInfo) {
(void) romInfo;
}
static Bit32u getROMCount() {
Bit32u count;
for(count = 0; getKnownROMInfoFromList(count) != NULL; count++) {
}
return count;
}
const ROMInfo** ROMInfo::getROMInfoList(Bit32u types, Bit32u pairTypes) {
const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1];
const ROMInfo **currentROMInList = romInfoList;
for (Bit32u i = 0; getKnownROMInfoFromList(i) != NULL; i++) {
const ROMInfo *romInfo = getKnownROMInfoFromList(i);
if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) {
*currentROMInList++ = romInfo;
}
}
*currentROMInList = NULL;
return romInfoList;
}
void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) {
delete[] romInfoList;
}
ROMImage::ROMImage(File *useFile) : file(useFile), romInfo(ROMInfo::getROMInfo(file))
{}
ROMImage::~ROMImage() {
ROMInfo::freeROMInfo(romInfo);
}
const ROMImage* ROMImage::makeROMImage(File *file) {
return new ROMImage(file);
}
void ROMImage::freeROMImage(const ROMImage *romImage) {
delete romImage;
}
File* ROMImage::getFile() const {
return file;
}
const ROMInfo* ROMImage::getROMInfo() const {
return romInfo;
}
} // namespace MT32Emu

80
src/SOUND/munt/ROMInfo.h Normal file
View File

@@ -0,0 +1,80 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_ROMINFO_H
#define MT32EMU_ROMINFO_H
#include <cstddef>
#include "globals.h"
#include "File.h"
namespace MT32Emu {
// Defines vital info about ROM file to be used by synth and applications
struct ROMInfo {
public:
size_t fileSize;
const File::SHA1Digest &sha1Digest;
enum Type {PCM, Control, Reverb} type;
const char *shortName;
const char *description;
enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType;
ROMInfo *pairROMInfo;
// Returns a ROMInfo struct by inspecting the size and the SHA1 hash
MT32EMU_EXPORT static const ROMInfo* getROMInfo(File *file);
// Currently no-op
MT32EMU_EXPORT static void freeROMInfo(const ROMInfo *romInfo);
// Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes
// (specified by bitmasks)
// Useful for GUI/console app to output information on what ROMs it supports
MT32EMU_EXPORT static const ROMInfo** getROMInfoList(Bit32u types, Bit32u pairTypes);
// Frees the list of ROMInfos given
MT32EMU_EXPORT static void freeROMInfoList(const ROMInfo **romInfos);
};
// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work
class ROMImage {
private:
File * const file;
const ROMInfo * const romInfo;
ROMImage(File *file);
~ROMImage();
public:
// Creates a ROMImage object given a ROMInfo and a File. Keeps a reference
// to the File and ROMInfo given, which must be freed separately by the user
// after the ROMImage is freed
MT32EMU_EXPORT static const ROMImage* makeROMImage(File *file);
// Must only be done after all Synths using the ROMImage are deleted
MT32EMU_EXPORT static void freeROMImage(const ROMImage *romImage);
MT32EMU_EXPORT File *getFile() const;
MT32EMU_EXPORT const ROMInfo *getROMInfo() const;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_ROMINFO_H

View File

@@ -0,0 +1,110 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SampleRateConverter.h"
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
#include "srchelper/SoxrAdapter.h"
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
#include "srchelper/SamplerateAdapter.h"
#else
#include "srchelper/InternalResampler.h"
#endif
#include "Synth.h"
using namespace MT32Emu;
static inline void *createDelegate(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) {
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
return new SoxrAdapter(synth, targetSampleRate, quality);
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
return new SamplerateAdapter(synth, targetSampleRate, quality);
#else
return new InternalResampler(synth, targetSampleRate, quality);
#endif
}
AnalogOutputMode SampleRateConverter::getBestAnalogOutputMode(double targetSampleRate) {
if (Synth::getStereoOutputSampleRate(AnalogOutputMode_ACCURATE) < targetSampleRate) {
return AnalogOutputMode_OVERSAMPLED;
} else if (Synth::getStereoOutputSampleRate(AnalogOutputMode_COARSE) < targetSampleRate) {
return AnalogOutputMode_ACCURATE;
}
return AnalogOutputMode_COARSE;
}
SampleRateConverter::SampleRateConverter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality useQuality) :
synthInternalToTargetSampleRateRatio(SAMPLE_RATE / targetSampleRate),
useSynthDelegate(useSynth.getStereoOutputSampleRate() == targetSampleRate),
srcDelegate(useSynthDelegate ? &useSynth : createDelegate(useSynth, targetSampleRate, useQuality))
{}
SampleRateConverter::~SampleRateConverter() {
if (!useSynthDelegate) {
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
delete static_cast<SoxrAdapter *>(srcDelegate);
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
delete static_cast<SamplerateAdapter *>(srcDelegate);
#else
delete static_cast<InternalResampler *>(srcDelegate);
#endif
}
}
void SampleRateConverter::getOutputSamples(float *buffer, unsigned int length) {
if (useSynthDelegate) {
static_cast<Synth *>(srcDelegate)->render(buffer, length);
return;
}
#if MT32EMU_WITH_LIBSOXR_RESAMPLER
static_cast<SoxrAdapter *>(srcDelegate)->getOutputSamples(buffer, length);
#elif MT32EMU_WITH_LIBSAMPLERATE_RESAMPLER
static_cast<SamplerateAdapter *>(srcDelegate)->getOutputSamples(buffer, length);
#else
static_cast<InternalResampler *>(srcDelegate)->getOutputSamples(buffer, length);
#endif
}
void SampleRateConverter::getOutputSamples(Bit16s *outBuffer, unsigned int length) {
static const unsigned int CHANNEL_COUNT = 2;
if (useSynthDelegate) {
static_cast<Synth *>(srcDelegate)->render(outBuffer, length);
return;
}
float floatBuffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN];
while (length > 0) {
const unsigned int size = MAX_SAMPLES_PER_RUN < length ? MAX_SAMPLES_PER_RUN : length;
getOutputSamples(floatBuffer, size);
float *outs = floatBuffer;
float *ends = floatBuffer + CHANNEL_COUNT * size;
while (outs < ends) {
*(outBuffer++) = Synth::convertSample(*(outs++));
}
length -= size;
}
}
double SampleRateConverter::convertOutputToSynthTimestamp(double outputTimestamp) const {
return outputTimestamp * synthInternalToTargetSampleRateRatio;
}
double SampleRateConverter::convertSynthToOutputTimestamp(double synthTimestamp) const {
return synthTimestamp / synthInternalToTargetSampleRateRatio;
}

View File

@@ -0,0 +1,71 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_SAMPLE_RATE_CONVERTER_H
#define MT32EMU_SAMPLE_RATE_CONVERTER_H
#include "globals.h"
#include "Types.h"
#include "Enumerations.h"
namespace MT32Emu {
class Synth;
/* SampleRateConverter class allows to convert the synthesiser output to any desired sample rate.
* It processes the completely mixed stereo output signal as it passes the analogue circuit emulation,
* so emulating the synthesiser output signal passing further through an ADC.
* Several conversion quality options are provided which allow to trade-off the conversion speed vs. the passband width.
* All the options except FASTEST guarantee full suppression of the aliasing noise in terms of the 16-bit integer samples.
*/
class MT32EMU_EXPORT SampleRateConverter {
public:
// Returns the value of AnalogOutputMode for which the output signal may retain its full frequency spectrum
// at the sample rate specified by the targetSampleRate argument.
static AnalogOutputMode getBestAnalogOutputMode(double targetSampleRate);
// Creates a SampleRateConverter instance that converts output signal from the synth to the given sample rate
// with the specified conversion quality.
SampleRateConverter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality);
~SampleRateConverter();
// Fills the provided output buffer with the results of the sample rate conversion.
// The input samples are automatically retrieved from the synth as necessary.
void getOutputSamples(MT32Emu::Bit16s *buffer, unsigned int length);
// Fills the provided output buffer with the results of the sample rate conversion.
// The input samples are automatically retrieved from the synth as necessary.
void getOutputSamples(float *buffer, unsigned int length);
// Returns the number of samples produced at the internal synth sample rate (32000 Hz)
// that correspond to the number of samples at the target sample rate.
// Intended to facilitate audio time synchronisation.
double convertOutputToSynthTimestamp(double outputTimestamp) const;
// Returns the number of samples produced at the target sample rate
// that correspond to the number of samples at the internal synth sample rate (32000 Hz).
// Intended to facilitate audio time synchronisation.
double convertSynthToOutputTimestamp(double synthTimestamp) const;
private:
const double synthInternalToTargetSampleRateRatio;
const bool useSynthDelegate;
void * const srcDelegate;
}; // class SampleRateConverter
} // namespace MT32Emu
#endif // MT32EMU_SAMPLE_RATE_CONVERTER_H

259
src/SOUND/munt/Structures.h Normal file
View File

@@ -0,0 +1,259 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_STRUCTURES_H
#define MT32EMU_STRUCTURES_H
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it
// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output
#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f))
#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f))
#ifdef _MSC_VER
#define MT32EMU_ALIGN_PACKED __declspec(align(1))
#else
#define MT32EMU_ALIGN_PACKED __attribute__((packed))
#endif
// The following structures represent the MT-32's memory
// Since sysex allows this memory to be written to in blocks of bytes,
// we keep this packed so that we can copy data into the various
// banks directly
#if defined(_MSC_VER) || defined(__MINGW32__)
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif
struct TimbreParam {
struct CommonParam {
char name[10];
Bit8u partialStructure12; // 1 & 2 0-12 (1-13)
Bit8u partialStructure34; // 3 & 4 0-12 (1-13)
Bit8u partialMute; // 0-15 (0000-1111)
Bit8u noSustain; // ENV MODE 0-1 (Normal, No sustain)
} MT32EMU_ALIGN_PACKED common;
struct PartialParam {
struct WGParam {
Bit8u pitchCoarse; // 0-96 (C1,C#1-C9)
Bit8u pitchFine; // 0-100 (-50 to +50 (cents - confirmed by Mok))
Bit8u pitchKeyfollow; // 0-16 (-1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2)
Bit8u pitchBenderEnabled; // 0-1 (OFF, ON)
Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2)
Bit8u pcmWave; // 0-127 (1-128)
Bit8u pulseWidth; // 0-100
Bit8u pulseWidthVeloSensitivity; // 0-14 (-7 - +7)
} MT32EMU_ALIGN_PACKED wg;
struct PitchEnvParam {
Bit8u depth; // 0-10
Bit8u veloSensitivity; // 0-100
Bit8u timeKeyfollow; // 0-4
Bit8u time[4]; // 0-100
Bit8u level[5]; // 0-100 (-50 - +50) // [3]: SUSTAIN LEVEL, [4]: END LEVEL
} MT32EMU_ALIGN_PACKED pitchEnv;
struct PitchLFOParam {
Bit8u rate; // 0-100
Bit8u depth; // 0-100
Bit8u modSensitivity; // 0-100
} MT32EMU_ALIGN_PACKED pitchLFO;
struct TVFParam {
Bit8u cutoff; // 0-100
Bit8u resonance; // 0-30
Bit8u keyfollow; // -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2
Bit8u biasPoint; // 0-127 (<1A-<7C >1A-7C)
Bit8u biasLevel; // 0-14 (-7 - +7)
Bit8u envDepth; // 0-100
Bit8u envVeloSensitivity; // 0-100
Bit8u envDepthKeyfollow; // DEPTH KEY FOLL0W 0-4
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
Bit8u envTime[5]; // 0-100
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
} MT32EMU_ALIGN_PACKED tvf;
struct TVAParam {
Bit8u level; // 0-100
Bit8u veloSensitivity; // 0-100
Bit8u biasPoint1; // 0-127 (<1A-<7C >1A-7C)
Bit8u biasLevel1; // 0-12 (-12 - 0)
Bit8u biasPoint2; // 0-127 (<1A-<7C >1A-7C)
Bit8u biasLevel2; // 0-12 (-12 - 0)
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
Bit8u envTimeVeloSensitivity; // VELOS KEY FOLL0W 0-4
Bit8u envTime[5]; // 0-100
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
} MT32EMU_ALIGN_PACKED tva;
} MT32EMU_ALIGN_PACKED partial[4]; // struct PartialParam
} MT32EMU_ALIGN_PACKED; // struct TimbreParam
struct PatchParam {
Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm)
Bit8u timbreNum; // TIMBRE NUMBER 0-63
Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones)
Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents)
Bit8u benderRange; // BENDER RANGE 0-24
Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4)
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
Bit8u dummy; // (DUMMY)
} MT32EMU_ALIGN_PACKED;
const unsigned int SYSTEM_MASTER_TUNE_OFF = 0;
const unsigned int SYSTEM_REVERB_MODE_OFF = 1;
const unsigned int SYSTEM_REVERB_TIME_OFF = 2;
const unsigned int SYSTEM_REVERB_LEVEL_OFF = 3;
const unsigned int SYSTEM_RESERVE_SETTINGS_START_OFF = 4;
const unsigned int SYSTEM_RESERVE_SETTINGS_END_OFF = 12;
const unsigned int SYSTEM_CHAN_ASSIGN_START_OFF = 13;
const unsigned int SYSTEM_CHAN_ASSIGN_END_OFF = 21;
const unsigned int SYSTEM_MASTER_VOL_OFF = 22;
struct MemParams {
// NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8.
// The LAPC-I documentation specified an additional area for rhythm at the end,
// where all parameters but fine tune, assign mode and output level are ignored
struct PatchTemp {
PatchParam patch;
Bit8u outputLevel; // OUTPUT LEVEL 0-100
Bit8u panpot; // PANPOT 0-14 (R-L)
Bit8u dummyv[6];
} MT32EMU_ALIGN_PACKED patchTemp[9];
struct RhythmTemp {
Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF); LAPC-I: 0-127 (M01-M64,R01-R63)
Bit8u outputLevel; // OUTPUT LEVEL 0-100
Bit8u panpot; // PANPOT 0-14 (R-L)
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
} MT32EMU_ALIGN_PACKED rhythmTemp[85];
TimbreParam timbreTemp[8];
PatchParam patches[128];
// NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above
struct PaddedTimbre {
TimbreParam timbre;
Bit8u padding[10];
} MT32EMU_ALIGN_PACKED timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
struct System {
Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz
Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay)
Bit8u reverbTime; // REVERB TIME 0-7 (1-8)
Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8)
Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32
Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF)
Bit8u masterVol; // MASTER VOLUME 0-100
} MT32EMU_ALIGN_PACKED system;
}; // struct MemParams
struct SoundGroup {
Bit8u timbreNumberTableAddrLow;
Bit8u timbreNumberTableAddrHigh;
Bit8u displayPosition;
Bit8u name[9];
Bit8u timbreCount;
Bit8u pad;
} MT32EMU_ALIGN_PACKED;
#if defined(_MSC_VER) || defined(__MINGW32__)
#pragma pack(pop)
#else
#pragma pack()
#endif
struct ControlROMFeatureSet {
unsigned int quirkPitchEnvelopeOverflow : 1;
// Features below don't actually depend on control ROM version, which is used to identify hardware model
unsigned int defaultReverbMT32Compatible : 1;
unsigned int oldMT32AnalogLPF : 1;
};
struct ControlROMMap {
const char *shortName;
const ControlROMFeatureSet &featureSet;
Bit16u pcmTable; // 4 * pcmCount bytes
Bit16u pcmCount;
Bit16u timbreAMap; // 128 bytes
Bit16u timbreAOffset;
bool timbreACompressed;
Bit16u timbreBMap; // 128 bytes
Bit16u timbreBOffset;
bool timbreBCompressed;
Bit16u timbreRMap; // 2 * timbreRCount bytes
Bit16u timbreRCount;
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
Bit16u rhythmSettingsCount;
Bit16u reserveSettings; // 9 bytes
Bit16u panSettings; // 8 bytes
Bit16u programSettings; // 8 bytes
Bit16u rhythmMaxTable; // 4 bytes
Bit16u patchMaxTable; // 16 bytes
Bit16u systemMaxTable; // 23 bytes
Bit16u timbreMaxTable; // 72 bytes
Bit16u soundGroupsTable; // 14 bytes each entry
Bit16u soundGroupsCount;
};
struct ControlROMPCMStruct {
Bit8u pos;
Bit8u len;
Bit8u pitchLSB;
Bit8u pitchMSB;
};
struct PCMWaveEntry {
Bit32u addr;
Bit32u len;
bool loop;
ControlROMPCMStruct *controlROMPCMStruct;
};
// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings
struct PatchCache {
bool playPartial;
bool PCMPartial;
int pcm;
Bit8u waveform;
Bit32u structureMix;
int structurePosition;
int structurePair;
// The following fields are actually common to all partials in the timbre
bool dirty;
Bit32u partialCount;
bool sustain;
bool reverb;
TimbreParam::PartialParam srcPartial;
// The following directly points into live sysex-addressable memory
const TimbreParam::PartialParam *partialParam;
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_STRUCTURES_H

2286
src/SOUND/munt/Synth.cpp Normal file

File diff suppressed because it is too large Load Diff

484
src/SOUND/munt/Synth.h Normal file
View File

@@ -0,0 +1,484 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_SYNTH_H
#define MT32EMU_SYNTH_H
#include <cstdarg>
#include <cstddef>
#include <cstring>
#include "globals.h"
#include "Types.h"
#include "Enumerations.h"
namespace MT32Emu {
class Analog;
class BReverbModel;
class Extensions;
class MemoryRegion;
class MidiEventQueue;
class Part;
class Poly;
class Partial;
class PartialManager;
class Renderer;
class ROMImage;
class PatchTempMemoryRegion;
class RhythmTempMemoryRegion;
class TimbreTempMemoryRegion;
class PatchesMemoryRegion;
class TimbresMemoryRegion;
class SystemMemoryRegion;
class DisplayMemoryRegion;
class ResetMemoryRegion;
struct ControlROMFeatureSet;
struct ControlROMMap;
struct PCMWaveEntry;
struct MemParams;
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
const Bit8u SYSEX_MDL_MT32 = 0x16;
const Bit8u SYSEX_MDL_D50 = 0x14;
const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1
const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1
const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data
const Bit8u SYSEX_CMD_RQD = 0x41; // Request data
const Bit8u SYSEX_CMD_DAT = 0x42; // Data set
const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge
const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
const Bit32u CONTROL_ROM_SIZE = 64 * 1024;
// Set of multiplexed output streams appeared at the DAC entrance.
template <class T>
struct DACOutputStreams {
T *nonReverbLeft;
T *nonReverbRight;
T *reverbDryLeft;
T *reverbDryRight;
T *reverbWetLeft;
T *reverbWetRight;
};
// Class for the client to supply callbacks for reporting various errors and information
class MT32EMU_EXPORT ReportHandler {
public:
virtual ~ReportHandler() {}
// Callback for debug messages, in vprintf() format
virtual void printDebug(const char *fmt, va_list list);
// Callbacks for reporting errors
virtual void onErrorControlROM() {}
virtual void onErrorPCMROM() {}
// Callback for reporting about displaying a new custom message on LCD
virtual void showLCDMessage(const char *message);
// Callback for reporting actual processing of a MIDI message
virtual void onMIDIMessagePlayed() {}
// Callback for reporting an overflow of the input MIDI queue.
// Returns true if a recovery action was taken and yet another attempt to enqueue the MIDI event is desired.
virtual bool onMIDIQueueOverflow() { return false; }
// Callback invoked when a System Realtime MIDI message is detected at the input.
virtual void onMIDISystemRealtime(Bit8u /* systemRealtime */) {}
// Callbacks for reporting system events
virtual void onDeviceReset() {}
virtual void onDeviceReconfig() {}
// Callbacks for reporting changes of reverb settings
virtual void onNewReverbMode(Bit8u /* mode */) {}
virtual void onNewReverbTime(Bit8u /* time */) {}
virtual void onNewReverbLevel(Bit8u /* level */) {}
// Callbacks for reporting various information
virtual void onPolyStateChanged(Bit8u /* partNum */) {}
virtual void onProgramChanged(Bit8u /* partNum */, const char * /* soundGroupName */, const char * /* patchName */) {}
};
class Synth {
friend class DefaultMidiStreamParser;
friend class Part;
friend class Partial;
friend class PartialManager;
friend class Poly;
friend class Renderer;
friend class RhythmPart;
friend class SamplerateAdapter;
friend class SoxrAdapter;
friend class TVA;
friend class TVP;
private:
// **************************** Implementation fields **************************
PatchTempMemoryRegion *patchTempMemoryRegion;
RhythmTempMemoryRegion *rhythmTempMemoryRegion;
TimbreTempMemoryRegion *timbreTempMemoryRegion;
PatchesMemoryRegion *patchesMemoryRegion;
TimbresMemoryRegion *timbresMemoryRegion;
SystemMemoryRegion *systemMemoryRegion;
DisplayMemoryRegion *displayMemoryRegion;
ResetMemoryRegion *resetMemoryRegion;
Bit8u *paddedTimbreMaxTable;
PCMWaveEntry *pcmWaves; // Array
const ControlROMFeatureSet *controlROMFeatures;
const ControlROMMap *controlROMMap;
Bit8u controlROMData[CONTROL_ROM_SIZE];
Bit16s *pcmROMData;
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit8u soundGroupIx[128]; // For each standard timbre
const char (*soundGroupNames)[9]; // Array
Bit32u partialCount;
Bit8u chantable[16]; // NOTE: value above 8 means that the channel is not assigned
MidiEventQueue *midiQueue;
volatile Bit32u lastReceivedMIDIEventTimestamp;
volatile Bit32u renderedSampleCount;
MemParams &mt32ram, &mt32default;
BReverbModel *reverbModels[4];
BReverbModel *reverbModel;
bool reverbOverridden;
MIDIDelayMode midiDelayMode;
DACInputMode dacInputMode;
float outputGain;
float reverbOutputGain;
bool reversedStereoEnabled;
bool opened;
bool activated;
bool isDefaultReportHandler;
ReportHandler *reportHandler;
PartialManager *partialManager;
Part *parts[9];
// When a partial needs to be aborted to free it up for use by a new Poly,
// the controller will busy-loop waiting for the sound to finish.
// We emulate this by delaying new MIDI events processing until abortion finishes.
Poly *abortingPoly;
Analog *analog;
Renderer *renderer;
// Binary compatibility helper.
Extensions &extensions;
// **************************** Implementation methods **************************
Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp);
bool isAbortingPoly() const { return abortingPoly != NULL; }
void readSysex(Bit8u channel, const Bit8u *sysex, Bit32u len) const;
void initMemoryRegions();
void deleteMemoryRegions();
MemoryRegion *findMemoryRegion(Bit32u addr);
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
bool loadControlROM(const ROMImage &controlROMImage);
bool loadPCMROM(const ROMImage &pcmROMImage);
bool initPCMList(Bit16u mapAddress, Bit16u count);
bool initTimbres(Bit16u mapAddress, Bit16u offset, Bit16u timbreCount, Bit16u startTimbre, bool compressed);
bool initCompressedTimbre(Bit16u drumNum, const Bit8u *mem, Bit32u memLen);
void initReverbModels(bool mt32CompatibleMode);
void initSoundGroups(char newSoundGroupNames[][9]);
void refreshSystemMasterTune();
void refreshSystemReverbParameters();
void refreshSystemReserveSettings();
void refreshSystemChanAssign(Bit8u firstPart, Bit8u lastPart);
void refreshSystemMasterVol();
void refreshSystem();
void reset();
void dispose();
void printPartialUsage(Bit32u sampleOffset = 0);
void newTimbreSet(Bit8u partNum, Bit8u timbreGroup, Bit8u timbreNumber, const char patchName[]);
void printDebug(const char *fmt, ...);
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
const Part *getPart(Bit8u partNum) const;
public:
static inline Bit16s clipSampleEx(Bit32s sampleEx) {
// Clamp values above 32767 to 32767, and values below -32768 to -32768
// FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain.
// The version below is actually a bit faster on my system...
//return ((sampleEx + 0x8000) & ~0xFFFF) ? Bit16s((sampleEx >> 31) ^ 0x7FFF) : (Bit16s)sampleEx;
return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? Bit16s(sampleEx) : Bit16s((sampleEx >> 31) ^ 0x7FFF);
}
static inline float clipSampleEx(float sampleEx) {
return sampleEx;
}
template <class S>
static inline void muteSampleBuffer(S *buffer, Bit32u len) {
if (buffer == NULL) return;
memset(buffer, 0, len * sizeof(S));
}
static inline void muteSampleBuffer(float *buffer, Bit32u len) {
if (buffer == NULL) return;
// FIXME: Use memset() where compatibility is guaranteed (if this turns out to be a win)
while (len--) {
*(buffer++) = 0.0f;
}
}
static inline Bit16s convertSample(float sample) {
return Synth::clipSampleEx(Bit32s(sample * 16384.0f)); // This multiplier takes into account the DAC bit shift
}
static inline float convertSample(Bit16s sample) {
return float(sample) / 16384.0f; // This multiplier takes into account the DAC bit shift
}
// Returns library version as an integer in format: 0x00MMmmpp, where:
// MM - major version number
// mm - minor version number
// pp - patch number
MT32EMU_EXPORT static Bit32u getLibraryVersionInt();
// Returns library version as a C-string in format: "MAJOR.MINOR.PATCH"
MT32EMU_EXPORT static const char *getLibraryVersionString();
MT32EMU_EXPORT static Bit32u getShortMessageLength(Bit32u msg);
MT32EMU_EXPORT static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0);
// Returns output sample rate used in emulation of stereo analog circuitry of hardware units.
// See comment for AnalogOutputMode.
MT32EMU_EXPORT static Bit32u getStereoOutputSampleRate(AnalogOutputMode analogOutputMode);
// Optionally sets callbacks for reporting various errors, information and debug messages
MT32EMU_EXPORT explicit Synth(ReportHandler *useReportHandler = NULL);
MT32EMU_EXPORT ~Synth();
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
// usePartialCount sets the maximum number of partials playing simultaneously for this session (optional).
// analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional).
MT32EMU_EXPORT bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, Bit32u usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE);
// Overloaded method which opens the synth with default partial count.
MT32EMU_EXPORT bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode);
// Closes the MT-32 and deallocates any memory used by the synthesizer
MT32EMU_EXPORT void close();
// Returns true if the synth is in completely initialized state, otherwise returns false.
MT32EMU_EXPORT bool isOpen() const;
// All the enqueued events are processed by the synth immediately.
MT32EMU_EXPORT void flushMIDIQueue();
// Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified.
// The queue is flushed before reallocation.
// Returns the actual queue size being used.
MT32EMU_EXPORT Bit32u setMIDIEventQueueSize(Bit32u);
// Enqueues a MIDI event for subsequent playback.
// The MIDI event will be processed not before the specified timestamp.
// The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
// The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface
// and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
// Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread.
// The methods return false if the MIDI event queue is full and the message cannot be enqueued.
// Enqueues a single short MIDI message to play at specified time. The message must contain a status byte.
MT32EMU_EXPORT bool playMsg(Bit32u msg, Bit32u timestamp);
// Enqueues a single well formed System Exclusive MIDI message to play at specified time.
MT32EMU_EXPORT bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp);
// Enqueues a single short MIDI message to be processed ASAP. The message must contain a status byte.
MT32EMU_EXPORT bool playMsg(Bit32u msg);
// Enqueues a single well formed System Exclusive MIDI message to be processed ASAP.
MT32EMU_EXPORT bool playSysex(const Bit8u *sysex, Bit32u len);
// WARNING:
// The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
// and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent.
// A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering.
// Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
// See the WARNING above.
MT32EMU_EXPORT void playMsgNow(Bit32u msg);
// Sends unpacked short MIDI message to the synth for immediate playback. The message must contain a status byte.
// See the WARNING above.
MT32EMU_EXPORT void playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity);
// Sends a single well formed System Exclusive MIDI message for immediate processing. The length is in bytes.
// See the WARNING above.
MT32EMU_EXPORT void playSysexNow(const Bit8u *sysex, Bit32u len);
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
// See the WARNING above.
MT32EMU_EXPORT void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len);
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
// See the WARNING above.
MT32EMU_EXPORT void playSysexWithoutHeader(Bit8u device, Bit8u command, const Bit8u *sysex, Bit32u len);
// Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
// See the WARNING above.
MT32EMU_EXPORT void writeSysex(Bit8u channel, const Bit8u *sysex, Bit32u len);
// Allows to disable wet reverb output altogether.
MT32EMU_EXPORT void setReverbEnabled(bool reverbEnabled);
// Returns whether wet reverb output is enabled.
MT32EMU_EXPORT bool isReverbEnabled() const;
// Sets override reverb mode. In this mode, emulation ignores sysexes (or the related part of them) which control the reverb parameters.
// This mode is in effect until it is turned off. When the synth is re-opened, the override mode is unchanged but the state
// of the reverb model is reset to default.
MT32EMU_EXPORT void setReverbOverridden(bool reverbOverridden);
// Returns whether reverb settings are overridden.
MT32EMU_EXPORT bool isReverbOverridden() const;
// Forces reverb model compatibility mode. By default, the compatibility mode corresponds to the used control ROM version.
// Invoking this method with the argument set to true forces emulation of old MT-32 reverb circuit.
// When the argument is false, emulation of the reverb circuit used in new generation of MT-32 compatible modules is enforced
// (these include CM-32L and LAPC-I).
MT32EMU_EXPORT void setReverbCompatibilityMode(bool mt32CompatibleMode);
// Returns whether reverb is in old MT-32 compatibility mode.
MT32EMU_EXPORT bool isMT32ReverbCompatibilityMode() const;
// Returns whether default reverb compatibility mode is the old MT-32 compatibility mode.
MT32EMU_EXPORT bool isDefaultReverbMT32Compatible() const;
// Sets new DAC input mode. See DACInputMode for details.
MT32EMU_EXPORT void setDACInputMode(DACInputMode mode);
// Returns current DAC input mode. See DACInputMode for details.
MT32EMU_EXPORT DACInputMode getDACInputMode() const;
// Sets new MIDI delay mode. See MIDIDelayMode for details.
MT32EMU_EXPORT void setMIDIDelayMode(MIDIDelayMode mode);
// Returns current MIDI delay mode. See MIDIDelayMode for details.
MT32EMU_EXPORT MIDIDelayMode getMIDIDelayMode() const;
// Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
// it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
// it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
// Ignored in DACInputMode_PURE
MT32EMU_EXPORT void setOutputGain(float gain);
// Returns current output gain factor for synth output channels.
MT32EMU_EXPORT float getOutputGain() const;
// Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
// analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability
// to control the gain of reverb and non-reverb output channels independently.
//
// Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely
// corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
// there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
// of that for LA32 analogue output. This factor is applied to the reverb output gain.
// Ignored in DACInputMode_PURE
MT32EMU_EXPORT void setReverbOutputGain(float gain);
// Returns current output gain factor for reverb wet output channels.
MT32EMU_EXPORT float getReverbOutputGain() const;
// Swaps left and right output channels.
MT32EMU_EXPORT void setReversedStereoEnabled(bool enabled);
// Returns whether left and right output channels are swapped.
MT32EMU_EXPORT bool isReversedStereoEnabled() const;
// Selects new type of the wave generator and renderer to be used during subsequent calls to open().
// By default, RendererType_BIT16S is selected.
// See RendererType for details.
MT32EMU_EXPORT void selectRendererType(RendererType);
// Returns previously selected type of the wave generator and renderer.
// See RendererType for details.
MT32EMU_EXPORT RendererType getSelectedRendererType() const;
// Returns actual sample rate used in emulation of stereo analog circuitry of hardware units.
// See comment for render() below.
MT32EMU_EXPORT Bit32u getStereoOutputSampleRate() const;
// Renders samples to the specified output stream as if they were sampled at the analog stereo output.
// When AnalogOutputMode is set to ACCURATE (OVERSAMPLED), the output signal is upsampled to 48 (96) kHz in order
// to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained.
// getStereoOutputSampleRate() can be used to query actual sample rate of the output signal.
// The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). Uses NATIVE byte ordering.
MT32EMU_EXPORT void render(Bit16s *stream, Bit32u len);
// Same as above but outputs to a float stereo stream.
MT32EMU_EXPORT void render(float *stream, Bit32u len);
// Renders samples to the specified output streams as if they appeared at the DAC entrance.
// No further processing performed in analog circuitry emulation is applied to the signal.
// NULL may be specified in place of any or all of the stream buffers to skip it.
// The length is in samples, not bytes. Uses NATIVE byte ordering.
MT32EMU_EXPORT void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
MT32EMU_EXPORT void renderStreams(const DACOutputStreams<Bit16s> &streams, Bit32u len);
// Same as above but outputs to float streams.
MT32EMU_EXPORT void renderStreams(float *nonReverbLeft, float *nonReverbRight, float *reverbDryLeft, float *reverbDryRight, float *reverbWetLeft, float *reverbWetRight, Bit32u len);
MT32EMU_EXPORT void renderStreams(const DACOutputStreams<float> &streams, Bit32u len);
// Returns true when there is at least one active partial, otherwise false.
MT32EMU_EXPORT bool hasActivePartials() const;
// Returns true if the synth is active and subsequent calls to render() may result in non-trivial output (i.e. silence).
// The synth is considered active when either there are pending MIDI events in the queue, there is at least one active partial,
// or the reverb is (somewhat unreliably) detected as being active.
MT32EMU_EXPORT bool isActive();
// Returns the maximum number of partials playing simultaneously.
MT32EMU_EXPORT Bit32u getPartialCount() const;
// Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts.
// If the value returned for a part is true, there is at least one active non-releasing partial playing on this part.
// This info is useful in emulating behaviour of LCD display of the hardware units.
MT32EMU_EXPORT void getPartStates(bool *partStates) const;
// Returns current states of all the parts as a bit set. The least significant bit corresponds to the state of part 1,
// total of 9 bits hold the states of all the parts. If the returned bit for a part is set, there is at least one active
// non-releasing partial playing on this part. This info is useful in emulating behaviour of LCD display of the hardware units.
MT32EMU_EXPORT Bit32u getPartStates() const;
// Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials.
MT32EMU_EXPORT void getPartialStates(PartialState *partialStates) const;
// Fills in current states of all the partials into the array provided. Each byte in the array holds states of 4 partials
// starting from the least significant bits. The state of each partial is packed in a pair of bits.
// The array must be large enough to accommodate states of all the partials (see getPartialCount()).
MT32EMU_EXPORT void getPartialStates(Bit8u *partialStates) const;
// Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
// to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials.
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
// Returns the number of currently playing notes on the specified part.
MT32EMU_EXPORT Bit32u getPlayingNotes(Bit8u partNumber, Bit8u *keys, Bit8u *velocities) const;
// Returns name of the patch set on the specified part.
// Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
MT32EMU_EXPORT const char *getPatchName(Bit8u partNumber) const;
// Stores internal state of emulated synth into an array provided (as it would be acquired from hardware).
MT32EMU_EXPORT void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
}; // class Synth
} // namespace MT32Emu
#endif // #ifndef MT32EMU_SYNTH_H

370
src/SOUND/munt/TVA.cpp Normal file
View File

@@ -0,0 +1,370 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* This class emulates the calculations performed by the 8095 microcontroller in order to configure the LA-32's amplitude ramp for a single partial at each stage of its TVA envelope.
* Unless we introduced bugs, it should be pretty much 100% accurate according to Mok's specifications.
*/
#include "internals.h"
#include "TVA.h"
#include "Part.h"
#include "Partial.h"
#include "Poly.h"
#include "Synth.h"
#include "Tables.h"
namespace MT32Emu {
// CONFIRMED: Matches a table in ROM - haven't got around to coming up with a formula for it yet.
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
partial(usePartial), ampRamp(useAmpRamp), system(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) {
}
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
target = newTarget;
phase = newPhase;
ampRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVA >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
#endif
}
void TVA::end(int newPhase) {
phase = newPhase;
playing = false;
#if MT32EMU_MONITOR_TVA >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,end,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newPhase);
#endif
}
static int multBias(Bit8u biasLevel, int bias) {
return (bias * biasLevelToAmpSubtractionCoeff[biasLevel]) >> 5;
}
static int calcBiasAmpSubtraction(Bit8u biasPoint, Bit8u biasLevel, int key) {
if ((biasPoint & 0x40) == 0) {
int bias = biasPoint + 33 - key;
if (bias > 0) {
return multBias(biasLevel, bias);
}
} else {
int bias = biasPoint - 31 - key;
if (bias < 0) {
bias = -bias;
return multBias(biasLevel, bias);
}
}
return 0;
}
static int calcBiasAmpSubtractions(const TimbreParam::PartialParam *partialParam, int key) {
int biasAmpSubtraction1 = calcBiasAmpSubtraction(partialParam->tva.biasPoint1, partialParam->tva.biasLevel1, key);
if (biasAmpSubtraction1 > 255) {
return 255;
}
int biasAmpSubtraction2 = calcBiasAmpSubtraction(partialParam->tva.biasPoint2, partialParam->tva.biasLevel2, key);
if (biasAmpSubtraction2 > 255) {
return 255;
}
int biasAmpSubtraction = biasAmpSubtraction1 + biasAmpSubtraction2;
if (biasAmpSubtraction > 255) {
return 255;
}
return biasAmpSubtraction;
}
static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) {
// FIXME:KG: Better variable names
int velocityMult = veloSensitivity - 50;
int absVelocityMult = velocityMult < 0 ? -velocityMult : velocityMult;
velocityMult = signed(unsigned(velocityMult * (signed(velocity) - 64)) << 2);
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
}
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
int amp = 155;
if (!partial->isRingModulatingSlave()) {
amp -= tables->masterVolToAmpSubtraction[system->masterVol];
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel];
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[expression];
if (amp < 0) {
return 0;
}
if (rhythmTemp != NULL) {
amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel];
if (amp < 0) {
return 0;
}
}
}
amp -= biasAmpSubtraction;
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[partialParam->tva.level];
if (amp < 0) {
return 0;
}
amp -= veloAmpSubtraction;
if (amp < 0) {
return 0;
}
if (amp > 155) {
amp = 155;
}
amp -= partialParam->tvf.resonance >> 1;
if (amp < 0) {
return 0;
}
return amp;
}
static int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) {
if (envTimeKeyfollow == 0) {
return 0;
}
return (key - 60) >> (5 - envTimeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
}
void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) {
part = newPart;
partialParam = newPartialParam;
patchTemp = newPart->getPatchTemp();
rhythmTemp = newRhythmTemp;
playing = true;
const Tables *tables = &Tables::getInstance();
int key = partial->getPoly()->getKey();
int velocity = partial->getPoly()->getVelocity();
keyTimeSubtraction = calcKeyTimeSubtraction(partialParam->tva.envTimeKeyfollow, key);
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
int newPhase;
if (partialParam->tva.envTime[0] == 0) {
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
// Note that this means that velocity never affects time for this partial.
newTarget += partialParam->tva.envLevel[0];
newPhase = TVA_PHASE_ATTACK; // The first target used in nextPhase() will be TVA_PHASE_2
} else {
// Initially go to the base amp determined by TVA level, part volume, etc., and spend the next phase going from there to the full TVA_PHASE_ATTACK target amp.
newPhase = TVA_PHASE_BASIC; // The first target used in nextPhase() will be TVA_PHASE_ATTACK
}
ampRamp->reset();//currentAmp = 0;
// "Go downward as quickly as possible".
// Since the current value is 0, the LA32Ramp will notice that we're already at or below the target and trying to go downward,
// and therefore jump to the target immediately and raise an interrupt.
startRamp(Bit8u(newTarget), 0x80 | 127, newPhase);
}
void TVA::startAbort() {
startRamp(64, 0x80 | 127, TVA_PHASE_RELEASE);
}
void TVA::startDecay() {
if (phase >= TVA_PHASE_RELEASE) {
return;
}
Bit8u newIncrement;
if (partialParam->tva.envTime[4] == 0) {
newIncrement = 1;
} else {
newIncrement = -partialParam->tva.envTime[4];
}
// The next time nextPhase() is called, it will think TVA_PHASE_RELEASE has finished and the partial will be aborted
startRamp(0, newIncrement, TVA_PHASE_RELEASE);
}
void TVA::handleInterrupt() {
nextPhase();
}
void TVA::recalcSustain() {
// We get pinged periodically by the pitch code to recalculate our values when in sustain.
// This is done so that the TVA will respond to things like MIDI expression and volume changes while it's sustaining, which it otherwise wouldn't do.
// The check for envLevel[3] == 0 strikes me as slightly dumb. FIXME: Explain why
if (phase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) {
return;
}
// We're sustaining. Recalculate all the values
const Tables *tables = &Tables::getInstance();
int newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
newTarget += partialParam->tva.envLevel[3];
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
int targetDelta = newTarget - target;
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
Bit8u newIncrement;
if (targetDelta >= 0) {
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - 2;
} else {
newIncrement = (tables->envLogarithmicTime[Bit8u(-targetDelta)] - 2) | 0x80;
}
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
}
bool TVA::isPlaying() const {
return playing;
}
int TVA::getPhase() const {
return phase;
}
void TVA::nextPhase() {
const Tables *tables = &Tables::getInstance();
if (phase >= TVA_PHASE_DEAD || !playing) {
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
return;
}
int newPhase = phase + 1;
if (newPhase == TVA_PHASE_DEAD) {
end(newPhase);
return;
}
bool allLevelsZeroFromNowOn = false;
if (partialParam->tva.envLevel[3] == 0) {
if (newPhase == TVA_PHASE_4) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[2] == 0) {
if (newPhase == TVA_PHASE_3) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[1] == 0) {
if (newPhase == TVA_PHASE_2) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[0] == 0) {
if (newPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions
allLevelsZeroFromNowOn = true;
}
}
}
}
}
int newTarget;
int newIncrement = 0; // Initialised to please compilers
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {
newTarget = calcBasicAmp(tables, partial, system, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
if (partialParam->tva.envLevel[3] == 0) {
end(newPhase);
return;
}
if (!partial->getPoly()->canSustain()) {
newPhase = TVA_PHASE_RELEASE;
newTarget = 0;
newIncrement = -partialParam->tva.envTime[4];
if (newIncrement == 0) {
// We can't let the increment be 0, or there would be no emulated interrupt.
// So we do an "upward" increment, which should set the amp to 0 extremely quickly
// and cause an "interrupt" to bring us back to nextPhase().
newIncrement = 1;
}
} else {
newTarget += partialParam->tva.envLevel[3];
newIncrement = 0;
}
} else {
newTarget += partialParam->tva.envLevel[envPointIndex];
}
} else {
newTarget = 0;
}
if ((newPhase != TVA_PHASE_SUSTAIN && newPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) {
int envTimeSetting = partialParam->tva.envTime[envPointIndex];
if (newPhase == TVA_PHASE_ATTACK) {
envTimeSetting -= (signed(partial->getPoly()->getVelocity()) - 64) >> (6 - partialParam->tva.envTimeVeloSensitivity); // PORTABILITY NOTE: Assumes arithmetic shift
if (envTimeSetting <= 0 && partialParam->tva.envTime[envPointIndex] != 0) {
envTimeSetting = 1;
}
} else {
envTimeSetting -= keyTimeSubtraction;
}
if (envTimeSetting > 0) {
int targetDelta = newTarget - target;
if (targetDelta <= 0) {
if (targetDelta == 0) {
// target and newTarget are the same.
// We can't have an increment of 0 or we wouldn't get an emulated interrupt.
// So instead make the target one less than it really should be and set targetDelta accordingly.
targetDelta = -1;
newTarget--;
if (newTarget < 0) {
// Oops, newTarget is less than zero now, so let's do it the other way:
// Make newTarget one more than it really should've been and set targetDelta accordingly.
// FIXME (apparent bug in real firmware):
// This means targetDelta will be positive just below here where it's inverted, and we'll end up using envLogarithmicTime[-1], and we'll be setting newIncrement to be descending later on, etc..
targetDelta = 1;
newTarget = -newTarget;
}
}
targetDelta = -targetDelta;
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
newIncrement = newIncrement | 0x80;
} else {
// FIXME: The last 22 or so entries in this table are 128 - surely that fucks things up, since that ends up being -128 signed?
newIncrement = tables->envLogarithmicTime[Bit8u(targetDelta)] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
}
} else {
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
}
// FIXME: What's the point of this? It's checked or set to non-zero everywhere above
if (newIncrement == 0) {
newIncrement = 1;
}
}
startRamp(Bit8u(newTarget), Bit8u(newIncrement), newPhase);
}
} // namespace MT32Emu

100
src/SOUND/munt/TVA.h Normal file
View File

@@ -0,0 +1,100 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVA_H
#define MT32EMU_TVA_H
#include "globals.h"
#include "Types.h"
#include "Structures.h"
namespace MT32Emu {
class LA32Ramp;
class Part;
class Partial;
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
// newPhase's value.
enum {
// In this phase, the base amp (as calculated in calcBasicAmp()) is targeted with an instant time.
// This phase is entered by reset() only if time[0] != 0.
TVA_PHASE_BASIC = 0,
// In this phase, level[0] is targeted within time[0], and velocity potentially affects time
TVA_PHASE_ATTACK = 1,
// In this phase, level[1] is targeted within time[1]
TVA_PHASE_2 = 2,
// In this phase, level[2] is targeted within time[2]
TVA_PHASE_3 = 3,
// In this phase, level[3] is targeted within time[3]
TVA_PHASE_4 = 4,
// In this phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
// Aborts the partial if level[3] is 0.
// Otherwise level[3] is continued, no phase change will occur until some external influence (like pedal release)
TVA_PHASE_SUSTAIN = 5,
// In this phase, 0 is targeted within time[4] (the time calculation is quite different from the other phases)
TVA_PHASE_RELEASE = 6,
// It's PHASE_DEAD, Jim.
TVA_PHASE_DEAD = 7
};
class TVA {
private:
const Partial * const partial;
LA32Ramp *ampRamp;
const MemParams::System * const system;
const Part *part;
const TimbreParam::PartialParam *partialParam;
const MemParams::PatchTemp *patchTemp;
const MemParams::RhythmTemp *rhythmTemp;
bool playing;
int biasAmpSubtraction;
int veloAmpSubtraction;
int keyTimeSubtraction;
Bit8u target;
int phase;
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
void end(int newPhase);
void nextPhase();
public:
TVA(const Partial *partial, LA32Ramp *ampRamp);
void reset(const Part *part, const TimbreParam::PartialParam *partialParam, const MemParams::RhythmTemp *rhythmTemp);
void handleInterrupt();
void recalcSustain();
void startDecay();
void startAbort();
bool isPlaying() const;
int getPhase() const;
}; // class TVA
} // namespace MT32Emu
#endif // #ifndef MT32EMU_TVA_H

233
src/SOUND/munt/TVF.cpp Normal file
View File

@@ -0,0 +1,233 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "internals.h"
#include "TVF.h"
#include "LA32Ramp.h"
#include "Partial.h"
#include "Poly.h"
#include "Tables.h"
namespace MT32Emu {
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
// newPhase's value.
enum {
// When this is the target phase, level[0] is targeted within time[0]
// Note that this phase is always set up in reset(), not nextPhase()
PHASE_ATTACK = 1,
// When this is the target phase, level[1] is targeted within time[1]
PHASE_2 = 2,
// When this is the target phase, level[2] is targeted within time[2]
PHASE_3 = 3,
// When this is the target phase, level[3] is targeted within time[3]
PHASE_4 = 4,
// When this is the target phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
// Otherwise level[3] is continued with increment 0 - no phase change will occur until some external influence (like pedal release)
PHASE_SUSTAIN = 5,
// 0 is targeted within time[4] (the time calculation is quite different from the other phases)
PHASE_RELEASE = 6,
// 0 is targeted with increment 0 (thus theoretically staying that way forever)
PHASE_DONE = 7
};
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
// This table matches the values used by a real LAPC-I.
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
// The table entries, when divided by 21, match approximately what the manual claims:
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
// Note that the entry for 1/8 is rounded to 2 (from 1/8 * 21 = 2.625), which seems strangely inaccurate compared to the others.
static const Bit8s keyfollowMult21[] = {-21, -10, -5, 0, 2, 5, 8, 10, 13, 16, 18, 21, 26, 32, 42, 21, 21};
int baseCutoff = keyfollowMult21[partialParam->tvf.keyfollow] - keyfollowMult21[partialParam->wg.pitchKeyfollow];
// baseCutoff range now: -63 to 63
baseCutoff *= int(key) - 60;
// baseCutoff range now: -3024 to 3024
int biasPoint = partialParam->tvf.biasPoint;
if ((biasPoint & 0x40) == 0) {
// biasPoint range here: 0 to 63
int bias = biasPoint + 33 - key; // bias range here: -75 to 84
if (bias > 0) {
bias = -bias; // bias range here: -1 to -84
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -7140 to 7140
// baseCutoff range now: -10164 to 10164
}
} else {
// biasPoint range here: 64 to 127
int bias = biasPoint - 31 - key; // bias range here: -75 to 84
if (bias < 0) {
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -6375 to 6375
// baseCutoff range now: -9399 to 9399
}
}
// baseCutoff range now: -10164 to 10164
baseCutoff += ((partialParam->tvf.cutoff << 4) - 800);
// baseCutoff range now: -10964 to 10964
if (baseCutoff >= 0) {
// FIXME: Potentially bad if baseCutoff ends up below -2056?
int pitchDeltaThing = (basePitch >> 4) + baseCutoff - 3584;
if (pitchDeltaThing > 0) {
baseCutoff -= pitchDeltaThing;
}
} else if (baseCutoff < -2048) {
baseCutoff = -2048;
}
baseCutoff += 2056;
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
if (baseCutoff > 255) {
baseCutoff = 255;
}
return Bit8u(baseCutoff);
}
TVF::TVF(const Partial *usePartial, LA32Ramp *useCutoffModifierRamp) :
partial(usePartial), cutoffModifierRamp(useCutoffModifierRamp) {
}
void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
target = newTarget;
phase = newPhase;
cutoffModifierRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
#endif
}
void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int basePitch) {
partialParam = newPartialParam;
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
const Tables *tables = &Tables::getInstance();
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
#endif
int newLevelMult = velocity * newPartialParam->tvf.envVeloSensitivity;
newLevelMult >>= 6;
newLevelMult += 109 - newPartialParam->tvf.envVeloSensitivity;
newLevelMult += (signed(key) - 60) >> (4 - newPartialParam->tvf.envDepthKeyfollow);
if (newLevelMult < 0) {
newLevelMult = 0;
}
newLevelMult *= newPartialParam->tvf.envDepth;
newLevelMult >>= 6;
if (newLevelMult > 255) {
newLevelMult = 255;
}
levelMult = newLevelMult;
if (newPartialParam->tvf.envTimeKeyfollow != 0) {
keyTimeSubtraction = (signed(key) - 60) >> (5 - newPartialParam->tvf.envTimeKeyfollow);
} else {
keyTimeSubtraction = 0;
}
int newTarget = (newLevelMult * newPartialParam->tvf.envLevel[0]) >> 8;
int envTimeSetting = newPartialParam->tvf.envTime[0] - keyTimeSubtraction;
int newIncrement;
if (envTimeSetting <= 0) {
newIncrement = (0x80 | 127);
} else {
newIncrement = tables->envLogarithmicTime[newTarget] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
}
cutoffModifierRamp->reset();
startRamp(newTarget, newIncrement, PHASE_2 - 1);
}
Bit8u TVF::getBaseCutoff() const {
return baseCutoff;
}
void TVF::handleInterrupt() {
nextPhase();
}
void TVF::startDecay() {
if (phase >= PHASE_RELEASE) {
return;
}
if (partialParam->tvf.envTime[4] == 0) {
startRamp(0, 1, PHASE_DONE - 1);
} else {
startRamp(0, -partialParam->tvf.envTime[4], PHASE_DONE - 1);
}
}
void TVF::nextPhase() {
const Tables *tables = &Tables::getInstance();
int newPhase = phase + 1;
switch (newPhase) {
case PHASE_DONE:
startRamp(0, 0, newPhase);
return;
case PHASE_SUSTAIN:
case PHASE_RELEASE:
// FIXME: Afaict newPhase should never be PHASE_RELEASE here. And if it were, this is an odd way to handle it.
if (!partial->getPoly()->canSustain()) {
phase = newPhase; // FIXME: Correct?
startDecay(); // FIXME: This should actually start decay even if phase is already 6. Does that matter?
return;
}
startRamp((levelMult * partialParam->tvf.envLevel[3]) >> 8, 0, newPhase);
return;
}
int envPointIndex = phase;
int envTimeSetting = partialParam->tvf.envTime[envPointIndex] - keyTimeSubtraction;
int newTarget = (levelMult * partialParam->tvf.envLevel[envPointIndex]) >> 8;
int newIncrement;
if (envTimeSetting > 0) {
int targetDelta = newTarget - target;
if (targetDelta == 0) {
if (newTarget == 0) {
targetDelta = 1;
newTarget = 1;
} else {
targetDelta = -1;
newTarget--;
}
}
newIncrement = tables->envLogarithmicTime[targetDelta < 0 ? -targetDelta : targetDelta] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
if (targetDelta < 0) {
newIncrement |= 0x80;
}
} else {
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
}
startRamp(newTarget, newIncrement, newPhase);
}
} // namespace MT32Emu

61
src/SOUND/munt/TVF.h Normal file
View File

@@ -0,0 +1,61 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVF_H
#define MT32EMU_TVF_H
#include "globals.h"
#include "Types.h"
#include "Structures.h"
namespace MT32Emu {
class LA32Ramp;
class Partial;
class TVF {
private:
const Partial * const partial;
LA32Ramp *cutoffModifierRamp;
const TimbreParam::PartialParam *partialParam;
Bit8u baseCutoff;
int keyTimeSubtraction;
unsigned int levelMult;
Bit8u target;
unsigned int phase;
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
void nextPhase();
public:
TVF(const Partial *partial, LA32Ramp *cutoffModifierRamp);
void reset(const TimbreParam::PartialParam *partialParam, Bit32u basePitch);
// Returns the base cutoff (without envelope modification).
// The base cutoff is calculated when reset() is called and remains static
// for the lifetime of the partial.
// Barring bugs, the number returned is confirmed accurate
// (based on specs from Mok).
Bit8u getBaseCutoff() const;
void handleInterrupt();
void startDecay();
}; // class TVF
} // namespace MT32Emu
#endif // #ifndef MT32EMU_TVF_H

330
src/SOUND/munt/TVP.cpp Normal file
View File

@@ -0,0 +1,330 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include "internals.h"
#include "TVP.h"
#include "Part.h"
#include "Partial.h"
#include "Poly.h"
#include "Synth.h"
#include "TVA.h"
namespace MT32Emu {
// FIXME: Add Explanation
static Bit16u lowerDurationToDivisor[] = {34078, 37162, 40526, 44194, 48194, 52556, 57312, 62499};
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
// The table matches exactly what the manual claims (when divided by 8192):
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
// ...except for the last two entries, which are supposed to be "1 cent above 1" and "2 cents above 1", respectively. They can only be roughly approximated with this integer math.
static Bit16s pitchKeyfollowMult[] = {-8192, -4096, -2048, 0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 16384, 8198, 8226};
// Note: Keys < 60 use keyToPitchTable[60 - key], keys >= 60 use keyToPitchTable[key - 60].
// FIXME: This table could really be shorter, since we never use e.g. key 127.
static Bit16u keyToPitchTable[] = {
0, 341, 683, 1024, 1365, 1707, 2048, 2389,
2731, 3072, 3413, 3755, 4096, 4437, 4779, 5120,
5461, 5803, 6144, 6485, 6827, 7168, 7509, 7851,
8192, 8533, 8875, 9216, 9557, 9899, 10240, 10581,
10923, 11264, 11605, 11947, 12288, 12629, 12971, 13312,
13653, 13995, 14336, 14677, 15019, 15360, 15701, 16043,
16384, 16725, 17067, 17408, 17749, 18091, 18432, 18773,
19115, 19456, 19797, 20139, 20480, 20821, 21163, 21504,
21845, 22187, 22528, 22869
};
TVP::TVP(const Partial *usePartial) :
partial(usePartial), system(&usePartial->getSynth()->mt32ram.system) {
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
maxCounter = SAMPLE_RATE / 4000;
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
// This is how much to increment it by every maxCounter samples.
processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE;
}
static Bit16s keyToPitch(unsigned int key) {
// We're using a table to do: return round_to_nearest_or_even((key - 60) * (4096.0 / 12.0))
// Banker's rounding is just slightly annoying to do in C++
int k = int(key);
Bit16s pitch = keyToPitchTable[abs(k - 60)];
return key < 60 ? -pitch : pitch;
}
static inline Bit32s coarseToPitch(Bit8u coarse) {
return (coarse - 36) * 4096 / 12; // One semitone per coarse offset
}
static inline Bit32s fineToPitch(Bit8u fine) {
return (fine - 50) * 4096 / 1200; // One cent per fine offset
}
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
Bit32s basePitch = keyToPitch(key);
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
basePitch += fineToPitch(partialParam->wg.pitchFine);
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
basePitch += fineToPitch(patchTemp->patch.fineTune);
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
if (controlROMPCMStruct != NULL) {
basePitch += (Bit32s(controlROMPCMStruct->pitchMSB) << 8) | Bit32s(controlROMPCMStruct->pitchLSB);
} else {
if ((partialParam->wg.waveform & 1) == 0) {
basePitch += 37133; // This puts Middle C at around 261.64Hz (assuming no other modifications, masterTune of 64, etc.)
} else {
// Sawtooth waves are effectively double the frequency of square waves.
// Thus we add 4096 less than for square waves here, which results in halving the frequency.
basePitch += 33037;
}
}
if (basePitch < 0) {
basePitch = 0;
}
if (basePitch > 59392) {
basePitch = 59392;
}
return Bit32u(basePitch);
}
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
if (veloSensitivity == 0 || veloSensitivity > 3) {
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
}
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
Bit32u veloMult = 32768;
veloMult -= (127 - velocity) << (5 + veloSensitivity);
veloMult *= 21845;
veloMult >>= 15;
return veloMult;
}
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
int veloMult = calcVeloMult(partialParam->pitchEnv.veloSensitivity, velocity);
int targetPitchOffsetWithoutLFO = partialParam->pitchEnv.level[levelIndex] - 50;
targetPitchOffsetWithoutLFO = (targetPitchOffsetWithoutLFO * veloMult) >> (16 - partialParam->pitchEnv.depth); // PORTABILITY NOTE: Assumes arithmetic shift
return targetPitchOffsetWithoutLFO;
}
void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartialParam) {
part = usePart;
partialParam = usePartialParam;
patchTemp = part->getPatchTemp();
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
timeElapsed = 0;
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
targetPitchOffsetWithoutLFO = currentPitchOffset;
phase = 0;
if (partialParam->pitchEnv.timeKeyfollow) {
timeKeyfollowSubtraction = Bit32s(key - 60) >> (5 - partialParam->pitchEnv.timeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
} else {
timeKeyfollowSubtraction = 0;
}
lfoPitchOffset = 0;
counter = 0;
pitch = basePitch;
// These don't really need to be initialised, but it aids debugging.
pitchOffsetChangePerBigTick = 0;
targetPitchOffsetReachedBigTick = 0;
shifts = 0;
}
Bit32u TVP::getBasePitch() const {
return basePitch;
}
void TVP::updatePitch() {
Bit32s newPitch = basePitch + currentPitchOffset;
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
// 171 is ~half a semitone.
newPitch += ((system->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
}
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
newPitch += part->getPitchBend();
}
if (newPitch < 0) {
newPitch = 0;
}
// Skipping this check seems about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning"
if (partial->getSynth()->controlROMFeatures->quirkPitchEnvelopeOverflow == 0) {
if (newPitch > 59392) {
newPitch = 59392;
}
}
pitch = Bit16u(newPitch);
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.
partial->getTVA()->recalcSustain();
}
void TVP::targetPitchOffsetReached() {
currentPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
switch (phase) {
case 3:
case 4:
{
int newLFOPitchOffset = (part->getModulation() * partialParam->pitchLFO.modSensitivity) >> 7;
newLFOPitchOffset = (newLFOPitchOffset + partialParam->pitchLFO.depth) << 1;
if (pitchOffsetChangePerBigTick > 0) {
// Go in the opposite direction to last time
newLFOPitchOffset = -newLFOPitchOffset;
}
lfoPitchOffset = newLFOPitchOffset;
int targetPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
setupPitchChange(targetPitchOffset, 101 - partialParam->pitchLFO.rate);
updatePitch();
break;
}
case 6:
updatePitch();
break;
default:
nextPhase();
}
}
void TVP::nextPhase() {
phase++;
int envIndex = phase == 6 ? 4 : phase;
targetPitchOffsetWithoutLFO = calcTargetPitchOffsetWithoutLFO(partialParam, envIndex, partial->getPoly()->getVelocity()); // pitch we'll reach at the end
int changeDuration = partialParam->pitchEnv.time[envIndex - 1];
changeDuration -= timeKeyfollowSubtraction;
if (changeDuration > 0) {
setupPitchChange(targetPitchOffsetWithoutLFO, changeDuration); // changeDuration between 0 and 112 now
updatePitch();
} else {
targetPitchOffsetReached();
}
}
// Shifts val to the left until bit 31 is 1 and returns the number of shifts
static Bit8u normalise(Bit32u &val) {
Bit8u leftShifts;
for (leftShifts = 0; leftShifts < 31; leftShifts++) {
if ((val & 0x80000000) != 0) {
break;
}
val = val << 1;
}
return leftShifts;
}
void TVP::setupPitchChange(int targetPitchOffset, Bit8u changeDuration) {
bool negativeDelta = targetPitchOffset < currentPitchOffset;
Bit32s pitchOffsetDelta = targetPitchOffset - currentPitchOffset;
if (pitchOffsetDelta > 32767 || pitchOffsetDelta < -32768) {
pitchOffsetDelta = 32767;
}
if (negativeDelta) {
pitchOffsetDelta = -pitchOffsetDelta;
}
// We want to maximise the number of bits of the Bit16s "pitchOffsetChangePerBigTick" we use in order to get the best possible precision later
Bit32u absPitchOffsetDelta = pitchOffsetDelta << 16;
Bit8u normalisationShifts = normalise(absPitchOffsetDelta); // FIXME: Double-check: normalisationShifts is usually between 0 and 15 here, unless the delta is 0, in which case it's 31
absPitchOffsetDelta = absPitchOffsetDelta >> 1; // Make room for the sign bit
changeDuration--; // changeDuration's now between 0 and 111
unsigned int upperDuration = changeDuration >> 3; // upperDuration's now between 0 and 13
shifts = normalisationShifts + upperDuration + 2;
Bit16u divisor = lowerDurationToDivisor[changeDuration & 7];
Bit16s newPitchOffsetChangePerBigTick = ((absPitchOffsetDelta & 0xFFFF0000) / divisor) >> 1; // Result now fits within 15 bits. FIXME: Check nothing's getting sign-extended incorrectly
if (negativeDelta) {
newPitchOffsetChangePerBigTick = -newPitchOffsetChangePerBigTick;
}
pitchOffsetChangePerBigTick = newPitchOffsetChangePerBigTick;
int currentBigTick = timeElapsed >> 8;
int durationInBigTicks = divisor >> (12 - upperDuration);
if (durationInBigTicks > 32767) {
durationInBigTicks = 32767;
}
// The result of the addition may exceed 16 bits, but wrapping is fine and intended here.
targetPitchOffsetReachedBigTick = currentBigTick + durationInBigTicks;
}
void TVP::startDecay() {
phase = 5;
lfoPitchOffset = 0;
targetPitchOffsetReachedBigTick = timeElapsed >> 8; // FIXME: Afaict there's no good reason for this - check
}
Bit16u TVP::nextPitch() {
// FIXME: Write explanation of counter and time increment
if (counter == 0) {
timeElapsed += processTimerIncrement;
timeElapsed = timeElapsed & 0x00FFFFFF;
process();
}
counter = (counter + 1) % maxCounter;
return pitch;
}
void TVP::process() {
if (phase == 0) {
targetPitchOffsetReached();
return;
}
if (phase == 5) {
nextPhase();
return;
}
if (phase > 7) {
updatePitch();
return;
}
Bit16s negativeBigTicksRemaining = (timeElapsed >> 8) - targetPitchOffsetReachedBigTick;
if (negativeBigTicksRemaining >= 0) {
// We've reached the time for a phase change
targetPitchOffsetReached();
return;
}
// FIXME: Write explanation for this stuff
int rightShifts = shifts;
if (rightShifts > 13) {
rightShifts -= 13;
negativeBigTicksRemaining = negativeBigTicksRemaining >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
rightShifts = 13;
}
int newResult = (negativeBigTicksRemaining * pitchOffsetChangePerBigTick) >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
newResult += targetPitchOffsetWithoutLFO + lfoPitchOffset;
currentPitchOffset = newResult;
updatePitch();
}
} // namespace MT32Emu

74
src/SOUND/munt/TVP.h Normal file
View File

@@ -0,0 +1,74 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVP_H
#define MT32EMU_TVP_H
#include "globals.h"
#include "Types.h"
#include "Structures.h"
namespace MT32Emu {
class Part;
class Partial;
class TVP {
private:
const Partial * const partial;
const MemParams::System * const system; // FIXME: Only necessary because masterTune calculation is done in the wrong place atm.
const Part *part;
const TimbreParam::PartialParam *partialParam;
const MemParams::PatchTemp *patchTemp;
int maxCounter;
int processTimerIncrement;
int counter;
Bit32u timeElapsed;
int phase;
Bit32u basePitch;
Bit32s targetPitchOffsetWithoutLFO;
Bit32s currentPitchOffset;
Bit16s lfoPitchOffset;
// In range -12 - 36
Bit8s timeKeyfollowSubtraction;
Bit16s pitchOffsetChangePerBigTick;
Bit16u targetPitchOffsetReachedBigTick;
unsigned int shifts;
Bit16u pitch;
void updatePitch();
void setupPitchChange(int targetPitchOffset, Bit8u changeDuration);
void targetPitchOffsetReached();
void nextPhase();
void process();
public:
TVP(const Partial *partial);
void reset(const Part *part, const TimbreParam::PartialParam *partialParam);
Bit32u getBasePitch() const;
Bit16u nextPitch();
void startDecay();
}; // class TVP
} // namespace MT32Emu
#endif // #ifndef MT32EMU_TVP_H

97
src/SOUND/munt/Tables.cpp Normal file
View File

@@ -0,0 +1,97 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "internals.h"
#include "Tables.h"
#include "mmath.h"
namespace MT32Emu {
// UNUSED: const int MIDDLEC = 60;
const Tables &Tables::getInstance() {
static const Tables instance;
return instance;
}
Tables::Tables() {
for (int lf = 0; lf <= 100; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
float fVal = (2.0f - LOG10F(float(lf) + 1.0f)) * 128.0f;
int val = int(fVal + 1.0);
if (val > 255) {
val = 255;
}
levelToAmpSubtraction[lf] = Bit8u(val);
}
envLogarithmicTime[0] = 64;
for (int lf = 1; lf <= 255; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
envLogarithmicTime[lf] = Bit8u(ceil(64.0f + LOG2F(float(lf)) * 8.0f));
}
#if 0
// The table below is to be used in conjunction with emulation of VCA of newer generation units which is currently missing.
// These relatively small values are rather intended to fine-tune the overall amplification of the VCA.
// CONFIRMED: Based on a table found by Mok in the LAPC-I control ROM
// Note that this matches the MT-32 table, but with the values clamped to a maximum of 8.
memset(masterVolToAmpSubtraction, 8, 71);
memset(masterVolToAmpSubtraction + 71, 7, 3);
memset(masterVolToAmpSubtraction + 74, 6, 4);
memset(masterVolToAmpSubtraction + 78, 5, 3);
memset(masterVolToAmpSubtraction + 81, 4, 4);
memset(masterVolToAmpSubtraction + 85, 3, 3);
memset(masterVolToAmpSubtraction + 88, 2, 4);
memset(masterVolToAmpSubtraction + 92, 1, 4);
memset(masterVolToAmpSubtraction + 96, 0, 5);
#else
// CONFIRMED: Based on a table found by Mok in the MT-32 control ROM
masterVolToAmpSubtraction[0] = 255;
for (int masterVol = 1; masterVol <= 100; masterVol++) {
masterVolToAmpSubtraction[masterVol] = Bit8u(106.31 - 16.0f * LOG2F(float(masterVol)));
}
#endif
for (int i = 0; i <= 100; i++) {
pulseWidth100To255[i] = Bit8u(i * 255 / 100.0f + 0.5f);
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
}
// The LA32 chip contains an exponent table inside. The table contains 12-bit integer values.
// The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address.
// To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also
// contains another 512-row table with inverted differences between the main table values.
for (int i = 0; i < 512; i++) {
exp9[i] = Bit16u(8191.5f - EXP2F(13.0f + ~i / 512.0f));
}
// There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values.
for (int i = 1; i < 512; i++) {
logsin9[i] = Bit16u(0.5f - LOG2F(sin((i + 0.5f) / 1024.0f * FLOAT_PI)) * 1024.0f);
}
// The very first value is clamped to the maximum possible 13-bit integer
logsin9[0] = 8191;
// found from sample analysis
static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1};
resAmpDecayFactor = resAmpDecayFactorTable;
}
} // namespace MT32Emu

62
src/SOUND/munt/Tables.h Normal file
View File

@@ -0,0 +1,62 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TABLES_H
#define MT32EMU_TABLES_H
#include "globals.h"
#include "Types.h"
namespace MT32Emu {
class Tables {
private:
Tables();
Tables(Tables &);
~Tables() {}
public:
static const Tables &getInstance();
// Constant LUTs
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
// - PatchTemp.outputLevel
// - RhythmTemp.outlevel
// - PartialParam.tva.level
// - expression
// It's used to determine how much to subtract from the amp envelope's target value
Bit8u levelToAmpSubtraction[101];
// CONFIRMED: ...
Bit8u envLogarithmicTime[256];
// CONFIRMED: ...
Bit8u masterVolToAmpSubtraction[101];
// CONFIRMED:
Bit8u pulseWidth100To255[101];
Bit16u exp9[512];
Bit16u logsin9[512];
const Bit8u *resAmpDecayFactor;
}; // class Tables
} // namespace MT32Emu
#endif // #ifndef MT32EMU_TABLES_H

32
src/SOUND/munt/Types.h Normal file
View File

@@ -0,0 +1,32 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TYPES_H
#define MT32EMU_TYPES_H
namespace MT32Emu {
typedef unsigned int Bit32u;
typedef signed int Bit32s;
typedef unsigned short int Bit16u;
typedef signed short int Bit16s;
typedef unsigned char Bit8u;
typedef signed char Bit8s;
}
#endif

View File

@@ -0,0 +1,635 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../globals.h"
#include "../Types.h"
#include "../File.h"
#include "../FileStream.h"
#include "../ROMInfo.h"
#include "../Synth.h"
#include "../MidiStreamParser.h"
#include "c_types.h"
#include "c_interface.h"
using namespace MT32Emu;
namespace MT32Emu {
static mt32emu_service_version getSynthVersionID(mt32emu_service_i) {
return MT32EMU_SERVICE_VERSION_CURRENT;
}
static const mt32emu_service_i_v0 SERVICE_VTABLE = {
getSynthVersionID,
mt32emu_get_supported_report_handler_version,
mt32emu_get_supported_midi_receiver_version,
mt32emu_get_library_version_int,
mt32emu_get_library_version_string,
mt32emu_get_stereo_output_samplerate,
mt32emu_create_context,
mt32emu_free_context,
mt32emu_add_rom_data,
mt32emu_add_rom_file,
mt32emu_get_rom_info,
mt32emu_set_partial_count,
mt32emu_set_analog_output_mode,
mt32emu_open_synth,
mt32emu_close_synth,
mt32emu_is_open,
mt32emu_get_actual_stereo_output_samplerate,
mt32emu_flush_midi_queue,
mt32emu_set_midi_event_queue_size,
mt32emu_set_midi_receiver,
mt32emu_parse_stream,
mt32emu_parse_stream_at,
mt32emu_play_short_message,
mt32emu_play_short_message_at,
mt32emu_play_msg,
mt32emu_play_sysex,
mt32emu_play_msg_at,
mt32emu_play_sysex_at,
mt32emu_play_msg_now,
mt32emu_play_msg_on_part,
mt32emu_play_sysex_now,
mt32emu_write_sysex,
mt32emu_set_reverb_enabled,
mt32emu_is_reverb_enabled,
mt32emu_set_reverb_overridden,
mt32emu_is_reverb_overridden,
mt32emu_set_reverb_compatibility_mode,
mt32emu_is_mt32_reverb_compatibility_mode,
mt32emu_is_default_reverb_mt32_compatible,
mt32emu_set_dac_input_mode,
mt32emu_get_dac_input_mode,
mt32emu_set_midi_delay_mode,
mt32emu_get_midi_delay_mode,
mt32emu_set_output_gain,
mt32emu_get_output_gain,
mt32emu_set_reverb_output_gain,
mt32emu_get_reverb_output_gain,
mt32emu_set_reversed_stereo_enabled,
mt32emu_is_reversed_stereo_enabled,
mt32emu_render_bit16s,
mt32emu_render_float,
mt32emu_render_bit16s_streams,
mt32emu_render_float_streams,
mt32emu_has_active_partials,
mt32emu_is_active,
mt32emu_get_partial_count,
mt32emu_get_part_states,
mt32emu_get_partial_states,
mt32emu_get_playing_notes,
mt32emu_get_patch_name,
mt32emu_read_memory
};
} // namespace MT32Emu
struct mt32emu_data {
ReportHandler *reportHandler;
Synth *synth;
const ROMImage *controlROMImage;
const ROMImage *pcmROMImage;
DefaultMidiStreamParser *midiParser;
Bit32u partialCount;
AnalogOutputMode analogOutputMode;
};
// Internal C++ utility stuff
namespace MT32Emu {
class DelegatingReportHandlerAdapter : public ReportHandler {
public:
DelegatingReportHandlerAdapter(mt32emu_report_handler_i useReportHandler, void *useInstanceData) :
delegate(useReportHandler), instanceData(useInstanceData) {}
protected:
const mt32emu_report_handler_i delegate;
void * const instanceData;
private:
void printDebug(const char *fmt, va_list list) {
if (delegate.v0->printDebug == NULL) {
ReportHandler::printDebug(fmt, list);
} else {
delegate.v0->printDebug(instanceData, fmt, list);
}
}
void onErrorControlROM() {
if (delegate.v0->onErrorControlROM == NULL) {
ReportHandler::onErrorControlROM();
} else {
delegate.v0->onErrorControlROM(instanceData);
}
}
void onErrorPCMROM() {
if (delegate.v0->onErrorPCMROM == NULL) {
ReportHandler::onErrorPCMROM();
} else {
delegate.v0->onErrorPCMROM(instanceData);
}
}
void showLCDMessage(const char *message) {
if (delegate.v0->showLCDMessage == NULL) {
ReportHandler::showLCDMessage(message);
} else {
delegate.v0->showLCDMessage(instanceData, message);
}
}
void onMIDIMessagePlayed() {
if (delegate.v0->onMIDIMessagePlayed == NULL) {
ReportHandler::onMIDIMessagePlayed();
} else {
delegate.v0->onMIDIMessagePlayed(instanceData);
}
}
bool onMIDIQueueOverflow() {
if (delegate.v0->onMIDIQueueOverflow == NULL) {
return ReportHandler::onMIDIQueueOverflow();
}
return delegate.v0->onMIDIQueueOverflow(instanceData) != MT32EMU_BOOL_FALSE;
}
void onMIDISystemRealtime(Bit8u systemRealtime) {
if (delegate.v0->onMIDISystemRealtime == NULL) {
ReportHandler::onMIDISystemRealtime(systemRealtime);
} else {
delegate.v0->onMIDISystemRealtime(instanceData, systemRealtime);
}
}
void onDeviceReset() {
if (delegate.v0->onDeviceReset == NULL) {
ReportHandler::onDeviceReset();
} else {
delegate.v0->onDeviceReset(instanceData);
}
}
void onDeviceReconfig() {
if (delegate.v0->onDeviceReconfig == NULL) {
ReportHandler::onDeviceReconfig();
} else {
delegate.v0->onDeviceReconfig(instanceData);
}
}
void onNewReverbMode(Bit8u mode) {
if (delegate.v0->onNewReverbMode == NULL) {
ReportHandler::onNewReverbMode(mode);
} else {
delegate.v0->onNewReverbMode(instanceData, mode);
}
}
void onNewReverbTime(Bit8u time) {
if (delegate.v0->onNewReverbTime == NULL) {
ReportHandler::onNewReverbTime(time);
} else {
delegate.v0->onNewReverbTime(instanceData, time);
}
}
void onNewReverbLevel(Bit8u level) {
if (delegate.v0->onNewReverbLevel == NULL) {
ReportHandler::onNewReverbLevel(level);
} else {
delegate.v0->onNewReverbLevel(instanceData, level);
}
}
void onPolyStateChanged(Bit8u partNum) {
if (delegate.v0->onPolyStateChanged == NULL) {
ReportHandler::onPolyStateChanged(partNum);
} else {
delegate.v0->onPolyStateChanged(instanceData, partNum);
}
}
void onProgramChanged(Bit8u partNum, const char *soundGroupName, const char *patchName) {
if (delegate.v0->onProgramChanged == NULL) {
ReportHandler::onProgramChanged(partNum, soundGroupName, patchName);
} else {
delegate.v0->onProgramChanged(instanceData, partNum, soundGroupName, patchName);
}
}
};
class DelegatingMidiStreamParser : public DefaultMidiStreamParser {
public:
DelegatingMidiStreamParser(const mt32emu_data *useData, mt32emu_midi_receiver_i useMIDIReceiver, void *useInstanceData) :
DefaultMidiStreamParser(*useData->synth), delegate(useMIDIReceiver), instanceData(useInstanceData) {}
protected:
mt32emu_midi_receiver_i delegate;
void *instanceData;
private:
void handleShortMessage(const Bit32u message) {
if (delegate.v0->handleShortMessage == NULL) {
DefaultMidiStreamParser::handleShortMessage(message);
} else {
delegate.v0->handleShortMessage(instanceData, message);
}
}
void handleSysex(const Bit8u *stream, const Bit32u length) {
if (delegate.v0->handleSysex == NULL) {
DefaultMidiStreamParser::handleSysex(stream, length);
} else {
delegate.v0->handleSysex(instanceData, stream, length);
}
}
void handleSystemRealtimeMessage(const Bit8u realtime) {
if (delegate.v0->handleSystemRealtimeMessage == NULL) {
DefaultMidiStreamParser::handleSystemRealtimeMessage(realtime);
} else {
delegate.v0->handleSystemRealtimeMessage(instanceData, realtime);
}
}
};
static mt32emu_return_code addROMFile(mt32emu_data *data, File *file) {
const ROMImage *image = ROMImage::makeROMImage(file);
const ROMInfo *info = image->getROMInfo();
if (info == NULL) {
ROMImage::freeROMImage(image);
return MT32EMU_RC_ROM_NOT_IDENTIFIED;
}
if (info->type == ROMInfo::Control) {
if (data->controlROMImage != NULL) {
delete data->controlROMImage->getFile();
ROMImage::freeROMImage(data->controlROMImage);
}
data->controlROMImage = image;
return MT32EMU_RC_ADDED_CONTROL_ROM;
} else if (info->type == ROMInfo::PCM) {
if (data->pcmROMImage != NULL) {
delete data->pcmROMImage->getFile();
ROMImage::freeROMImage(data->pcmROMImage);
}
data->pcmROMImage = image;
return MT32EMU_RC_ADDED_PCM_ROM;
}
ROMImage::freeROMImage(image);
return MT32EMU_RC_OK; // No support for reverb ROM yet.
}
} // namespace MT32Emu
// C-visible implementation
extern "C" {
mt32emu_service_i mt32emu_get_service_i() {
mt32emu_service_i i;
i.v0 = &SERVICE_VTABLE;
return i;
}
mt32emu_report_handler_version mt32emu_get_supported_report_handler_version() {
return MT32EMU_REPORT_HANDLER_VERSION_CURRENT;
}
mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version() {
return MT32EMU_MIDI_RECEIVER_VERSION_CURRENT;
}
mt32emu_bit32u mt32emu_get_library_version_int() {
return Synth::getLibraryVersionInt();
}
const char *mt32emu_get_library_version_string() {
return Synth::getLibraryVersionString();
}
mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode) {
return Synth::getStereoOutputSampleRate(static_cast<AnalogOutputMode>(analog_output_mode));
}
mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data) {
mt32emu_data *data = new mt32emu_data;
data->reportHandler = (report_handler.v0 != NULL) ? new DelegatingReportHandlerAdapter(report_handler, instance_data) : new ReportHandler;
data->synth = new Synth(data->reportHandler);
data->midiParser = new DefaultMidiStreamParser(*data->synth);
data->controlROMImage = NULL;
data->pcmROMImage = NULL;
data->partialCount = DEFAULT_MAX_PARTIALS;
data->analogOutputMode = AnalogOutputMode_COARSE;
return data;
}
void mt32emu_free_context(mt32emu_context data) {
if (data == NULL) return;
if (data->controlROMImage != NULL) {
delete data->controlROMImage->getFile();
ROMImage::freeROMImage(data->controlROMImage);
data->controlROMImage = NULL;
}
if (data->pcmROMImage != NULL) {
delete data->pcmROMImage->getFile();
ROMImage::freeROMImage(data->pcmROMImage);
data->pcmROMImage = NULL;
}
delete data->midiParser;
data->midiParser = NULL;
delete data->synth;
data->synth = NULL;
delete data->reportHandler;
data->reportHandler = NULL;
delete data;
}
mt32emu_return_code mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest) {
if (sha1_digest == NULL) return addROMFile(context, new ArrayFile(data, data_size));
return addROMFile(context, new ArrayFile(data, data_size, *sha1_digest));
}
mt32emu_return_code mt32emu_add_rom_file(mt32emu_context context, const char *filename) {
mt32emu_return_code rc = MT32EMU_RC_OK;
FileStream *fs = new FileStream;
if (fs->open(filename)) {
if (fs->getData() != NULL) {
rc = addROMFile(context, fs);
if (rc > 0) return rc;
} else {
rc = MT32EMU_RC_FILE_NOT_LOADED;
}
} else {
rc = MT32EMU_RC_FILE_NOT_FOUND;
}
delete fs;
return rc;
}
void mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info) {
const ROMInfo *romInfo = context->controlROMImage == NULL ? NULL : context->controlROMImage->getROMInfo();
if (romInfo != NULL) {
rom_info->control_rom_id = romInfo->shortName;
rom_info->control_rom_description = romInfo->description;
rom_info->control_rom_sha1_digest = romInfo->sha1Digest;
} else {
rom_info->control_rom_id = NULL;
rom_info->control_rom_description = NULL;
rom_info->control_rom_sha1_digest = NULL;
}
romInfo = context->pcmROMImage == NULL ? NULL : context->pcmROMImage->getROMInfo();
if (romInfo != NULL) {
rom_info->pcm_rom_id = romInfo->shortName;
rom_info->pcm_rom_description = romInfo->description;
rom_info->pcm_rom_sha1_digest = romInfo->sha1Digest;
} else {
rom_info->pcm_rom_id = NULL;
rom_info->pcm_rom_description = NULL;
rom_info->pcm_rom_sha1_digest = NULL;
}
}
void mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count) {
context->partialCount = partial_count;
}
void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode) {
context->analogOutputMode = static_cast<AnalogOutputMode>(analog_output_mode);
}
void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type) {
context->synth->selectRendererType(static_cast<RendererType>(renderer_type));
}
mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context) {
return static_cast<mt32emu_renderer_type>(context->synth->getSelectedRendererType());
}
mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context) {
if ((context->controlROMImage == NULL) || (context->pcmROMImage == NULL)) {
return MT32EMU_RC_MISSING_ROMS;
}
if (!context->synth->open(*context->controlROMImage, *context->pcmROMImage, context->partialCount, context->analogOutputMode)) {
return MT32EMU_RC_FAILED;
}
return MT32EMU_RC_OK;
}
void mt32emu_close_synth(mt32emu_const_context context) {
context->synth->close();
}
mt32emu_boolean mt32emu_is_open(mt32emu_const_context context) {
return context->synth->isOpen() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context) {
return context->synth->getStereoOutputSampleRate();
}
void mt32emu_flush_midi_queue(mt32emu_const_context context) {
context->synth->flushMIDIQueue();
}
mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size) {
return context->synth->setMIDIEventQueueSize(queue_size);
}
void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data) {
delete context->midiParser;
context->midiParser = (midi_receiver.v0 != NULL) ? new DelegatingMidiStreamParser(context, midi_receiver, instance_data) : new DefaultMidiStreamParser(*context->synth);
}
void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length) {
context->midiParser->resetTimestamp();
context->midiParser->parseStream(stream, length);
}
void mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp) {
context->midiParser->setTimestamp(timestamp);
context->midiParser->parseStream(stream, length);
}
void mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message) {
context->midiParser->resetTimestamp();
context->midiParser->processShortMessage(message);
}
void mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp) {
context->midiParser->setTimestamp(timestamp);
context->midiParser->processShortMessage(message);
}
mt32emu_return_code mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg) {
if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
return (context->synth->playMsg(msg)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
}
mt32emu_return_code mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
return (context->synth->playSysex(sysex, len)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
}
mt32emu_return_code mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp) {
if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
return (context->synth->playMsg(msg, timestamp)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
}
mt32emu_return_code mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp) {
if (!context->synth->isOpen()) return MT32EMU_RC_NOT_OPENED;
return (context->synth->playSysex(sysex, len, timestamp)) ? MT32EMU_RC_OK : MT32EMU_RC_QUEUE_FULL;
}
void mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg) {
context->synth->playMsgNow(msg);
}
void mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity) {
context->synth->playMsgOnPart(part, code, note, velocity);
}
void mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
context->synth->playSysexNow(sysex, len);
}
void mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len) {
context->synth->writeSysex(channel, sysex, len);
}
void mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled) {
context->synth->setReverbEnabled(reverb_enabled != MT32EMU_BOOL_FALSE);
}
mt32emu_boolean mt32emu_is_reverb_enabled(mt32emu_const_context context) {
return context->synth->isReverbEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden) {
context->synth->setReverbOverridden(reverb_overridden != MT32EMU_BOOL_FALSE);
}
mt32emu_boolean mt32emu_is_reverb_overridden(mt32emu_const_context context) {
return context->synth->isReverbOverridden() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode) {
context->synth->setReverbCompatibilityMode(mt32_compatible_mode != MT32EMU_BOOL_FALSE);
}
mt32emu_boolean mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context) {
return context->synth->isMT32ReverbCompatibilityMode() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
mt32emu_boolean mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context) {
return context->synth->isDefaultReverbMT32Compatible() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode) {
context->synth->setDACInputMode(static_cast<DACInputMode>(mode));
}
mt32emu_dac_input_mode mt32emu_get_dac_input_mode(mt32emu_const_context context) {
return static_cast<mt32emu_dac_input_mode>(context->synth->getDACInputMode());
}
void mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode) {
context->synth->setMIDIDelayMode(static_cast<MIDIDelayMode>(mode));
}
mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const_context context) {
return static_cast<mt32emu_midi_delay_mode>(context->synth->getMIDIDelayMode());
}
void mt32emu_set_output_gain(mt32emu_const_context context, float gain) {
context->synth->setOutputGain(gain);
}
float mt32emu_get_output_gain(mt32emu_const_context context) {
return context->synth->getOutputGain();
}
void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain) {
context->synth->setReverbOutputGain(gain);
}
float mt32emu_get_reverb_output_gain(mt32emu_const_context context) {
return context->synth->getReverbOutputGain();
}
void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled) {
context->synth->setReversedStereoEnabled(enabled != MT32EMU_BOOL_FALSE);
}
mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context) {
return context->synth->isReversedStereoEnabled() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len) {
context->synth->render(stream, len);
}
void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len) {
context->synth->render(stream, len);
}
void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len) {
context->synth->renderStreams(*reinterpret_cast<const DACOutputStreams<Bit16s> *>(streams), len);
}
void mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len) {
context->synth->renderStreams(*reinterpret_cast<const DACOutputStreams<float> *>(streams), len);
}
mt32emu_boolean mt32emu_has_active_partials(mt32emu_const_context context) {
return context->synth->hasActivePartials() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
mt32emu_boolean mt32emu_is_active(mt32emu_const_context context) {
return context->synth->isActive() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
mt32emu_bit32u mt32emu_get_partial_count(mt32emu_const_context context) {
return context->synth->getPartialCount();
}
mt32emu_bit32u mt32emu_get_part_states(mt32emu_const_context context) {
return context->synth->getPartStates();
}
void mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states) {
context->synth->getPartialStates(partial_states);
}
mt32emu_bit32u mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities) {
return context->synth->getPlayingNotes(part_number, keys, velocities);
}
const char *mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number) {
return context->synth->getPatchName(part_number);
}
void mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data) {
context->synth->readMemory(addr, len, data);
}
} // extern "C"

View File

@@ -0,0 +1,418 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_C_INTERFACE_H
#define MT32EMU_C_INTERFACE_H
#include <stddef.h>
#include "../globals.h"
#include "c_types.h"
#undef MT32EMU_EXPORT
#define MT32EMU_EXPORT MT32EMU_EXPORT_ATTRIBUTE
#ifdef __cplusplus
extern "C" {
#endif
/* == Context-independent functions == */
/* === Interface handling === */
/** Returns mt32emu_service_i interface. */
MT32EMU_EXPORT mt32emu_service_i mt32emu_get_service_i();
#if MT32EMU_EXPORTS_TYPE == 2
#undef MT32EMU_EXPORT
#define MT32EMU_EXPORT
#endif
/**
* Returns the version ID of mt32emu_report_handler_i interface the library has been compiled with.
* This allows a client to fall-back gracefully instead of silently not receiving expected event reports.
*/
MT32EMU_EXPORT mt32emu_report_handler_version mt32emu_get_supported_report_handler_version();
/**
* Returns the version ID of mt32emu_midi_receiver_version_i interface the library has been compiled with.
* This allows a client to fall-back gracefully instead of silently not receiving expected MIDI messages.
*/
MT32EMU_EXPORT mt32emu_midi_receiver_version mt32emu_get_supported_midi_receiver_version();
/**
* Returns library version as an integer in format: 0x00MMmmpp, where:
* MM - major version number
* mm - minor version number
* pp - patch number
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_library_version_int();
/**
* Returns library version as a C-string in format: "MAJOR.MINOR.PATCH".
*/
MT32EMU_EXPORT const char *mt32emu_get_library_version_string();
/**
* Returns output sample rate used in emulation of stereo analog circuitry of hardware units for particular analog_output_mode.
* See comment for mt32emu_analog_output_mode.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_stereo_output_samplerate(const mt32emu_analog_output_mode analog_output_mode);
/**
* Returns the value of analog_output_mode for which the output signal may retain its full frequency spectrum
* at the sample rate specified by the target_samplerate argument.
* See comment for mt32emu_analog_output_mode.
*/
MT32EMU_EXPORT mt32emu_analog_output_mode mt32emu_get_best_analog_output_mode(const double target_samplerate);
/* == Context-dependent functions == */
/** Initialises a new emulation context and installs custom report handler if non-NULL. */
MT32EMU_EXPORT mt32emu_context mt32emu_create_context(mt32emu_report_handler_i report_handler, void *instance_data);
/** Closes and destroys emulation context. */
MT32EMU_EXPORT void mt32emu_free_context(mt32emu_context context);
/**
* Adds new ROM identified by its SHA1 digest to the emulation context replacing previously added ROM of the same type if any.
* Argument sha1_digest can be NULL, in this case the digest will be computed using the actual ROM data.
* If sha1_digest is set to non-NULL, it is assumed being correct and will not be recomputed.
* This function doesn't immediately change the state of already opened synth. Newly added ROM will take effect upon next call of mt32emu_open_synth().
* Returns positive value upon success.
*/
MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_data(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest);
/**
* Loads a ROM file, identify it by SHA1 digest, and adds it to the emulation context replacing previously added ROM of the same type if any.
* This function doesn't immediately change the state of already opened synth. Newly added ROM will take effect upon next call of mt32emu_open_synth().
* Returns positive value upon success.
*/
MT32EMU_EXPORT mt32emu_return_code mt32emu_add_rom_file(mt32emu_context context, const char *filename);
/**
* Fills in mt32emu_rom_info structure with identifiers and descriptions of control and PCM ROM files identified and added to the synth context.
* If one of the ROM files is not loaded and identified yet, NULL is returned in the corresponding fields of the mt32emu_rom_info structure.
*/
MT32EMU_EXPORT void mt32emu_get_rom_info(mt32emu_const_context context, mt32emu_rom_info *rom_info);
/**
* Allows to override the default maximum number of partials playing simultaneously within the emulation session.
* This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth().
*/
MT32EMU_EXPORT void mt32emu_set_partial_count(mt32emu_context context, const mt32emu_bit32u partial_count);
/**
* Allows to override the default mode for emulation of analogue circuitry of the hardware units within the emulation session.
* This function doesn't immediately change the state of already opened synth. Newly set value will take effect upon next call of mt32emu_open_synth().
*/
MT32EMU_EXPORT void mt32emu_set_analog_output_mode(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode);
/**
* Allows to convert the synthesiser output to any desired sample rate. The samplerate conversion
* processes the completely mixed stereo output signal as it passes the analogue circuit emulation,
* so emulating the synthesiser output signal passing further through an ADC. When the samplerate
* argument is set to 0, the default output sample rate is used which depends on the current
* mode of analog circuitry emulation. See mt32emu_analog_output_mode.
* This function doesn't immediately change the state of already opened synth.
* Newly set value will take effect upon next call of mt32emu_open_synth().
*/
MT32EMU_EXPORT void mt32emu_set_stereo_output_samplerate(mt32emu_context context, const double samplerate);
/**
* Several samplerate conversion quality options are provided which allow to trade-off the conversion speed vs.
* the retained passband width. All the options except FASTEST guarantee full suppression of the aliasing noise
* in terms of the 16-bit integer samples.
* This function doesn't immediately change the state of already opened synth.
* Newly set value will take effect upon next call of mt32emu_open_synth().
*/
MT32EMU_EXPORT void mt32emu_set_samplerate_conversion_quality(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality);
/**
* Selects new type of the wave generator and renderer to be used during subsequent calls to mt32emu_open_synth().
* By default, MT32EMU_RT_BIT16S is selected.
* See mt32emu_renderer_type for details.
*/
MT32EMU_EXPORT void mt32emu_select_renderer_type(mt32emu_context context, const mt32emu_renderer_type renderer_type);
/**
* Returns previously selected type of the wave generator and renderer.
* See mt32emu_renderer_type for details.
*/
MT32EMU_EXPORT mt32emu_renderer_type mt32emu_get_selected_renderer_type(mt32emu_context context);
/**
* Prepares the emulation context to receive MIDI messages and produce output audio data using aforehand added set of ROMs,
* and optionally set the maximum partial count and the analog output mode.
* Returns MT32EMU_RC_OK upon success.
*/
MT32EMU_EXPORT mt32emu_return_code mt32emu_open_synth(mt32emu_const_context context);
/** Closes the emulation context freeing allocated resources. Added ROMs remain unaffected and ready for reuse. */
MT32EMU_EXPORT void mt32emu_close_synth(mt32emu_const_context context);
/** Returns true if the synth is in completely initialized state, otherwise returns false. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_open(mt32emu_const_context context);
/**
* Returns actual sample rate of the fully processed output stereo signal.
* If samplerate conversion is used (i.e. when mt32emu_set_stereo_output_samplerate() has been invoked with a non-zero value),
* the returned value is the desired output samplerate rounded down to the closest integer.
* Otherwise, the output samplerate is choosen depending on the emulation mode of stereo analog circuitry of hardware units.
* See comment for mt32emu_analog_output_mode for more info.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_actual_stereo_output_samplerate(mt32emu_const_context context);
/**
* Returns the number of samples produced at the internal synth sample rate (32000 Hz)
* that correspond to the given number of samples at the output sample rate.
* Intended to facilitate audio time synchronisation.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_output_to_synth_timestamp(mt32emu_const_context context, mt32emu_bit32u output_timestamp);
/**
* Returns the number of samples produced at the output sample rate
* that correspond to the given number of samples at the internal synth sample rate (32000 Hz).
* Intended to facilitate audio time synchronisation.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_convert_synth_to_output_timestamp(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
/** All the enqueued events are processed by the synth immediately. */
MT32EMU_EXPORT void mt32emu_flush_midi_queue(mt32emu_const_context context);
/**
* Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified.
* The queue is flushed before reallocation.
* Returns the actual queue size being used.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_set_midi_event_queue_size(mt32emu_const_context context, const mt32emu_bit32u queue_size);
/**
* Installs custom MIDI receiver object intended for receiving MIDI messages generated by MIDI stream parser.
* MIDI stream parser is involved when functions mt32emu_parse_stream() and mt32emu_play_short_message() or the likes are called.
* By default, parsed short MIDI messages and System Exclusive messages are sent to the synth input MIDI queue.
* This function allows to override default behaviour. If midi_receiver argument is set to NULL, the default behaviour is restored.
*/
MT32EMU_EXPORT void mt32emu_set_midi_receiver(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data);
/* Enqueues a MIDI event for subsequent playback.
* The MIDI event will be processed not before the specified timestamp.
* The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz).
* The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface
* and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
* Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread.
* onMIDIQueueOverflow callback is invoked when the MIDI event queue is full and the message cannot be enqueued.
*/
/**
* Parses a block of raw MIDI bytes and enqueues parsed MIDI messages for further processing ASAP.
* SysEx messages are allowed to be fragmented across several calls to this method. Running status is also handled for short messages.
* When a System Realtime MIDI message is parsed, onMIDISystemRealtime callback is invoked.
* NOTE: the total length of a SysEx message being fragmented shall not exceed MT32EMU_MAX_STREAM_BUFFER_SIZE (32768 bytes).
*/
MT32EMU_EXPORT void mt32emu_parse_stream(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length);
/**
* Parses a block of raw MIDI bytes and enqueues parsed MIDI messages to play at specified time.
* SysEx messages are allowed to be fragmented across several calls to this method. Running status is also handled for short messages.
* When a System Realtime MIDI message is parsed, onMIDISystemRealtime callback is invoked.
* NOTE: the total length of a SysEx message being fragmented shall not exceed MT32EMU_MAX_STREAM_BUFFER_SIZE (32768 bytes).
*/
MT32EMU_EXPORT void mt32emu_parse_stream_at(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp);
/**
* Enqueues a single mt32emu_bit32u-encoded short MIDI message with full processing ASAP.
* The short MIDI message may contain no status byte, the running status is used in this case.
* When the argument is a System Realtime MIDI message, onMIDISystemRealtime callback is invoked.
*/
MT32EMU_EXPORT void mt32emu_play_short_message(mt32emu_const_context context, mt32emu_bit32u message);
/**
* Enqueues a single mt32emu_bit32u-encoded short MIDI message to play at specified time with full processing.
* The short MIDI message may contain no status byte, the running status is used in this case.
* When the argument is a System Realtime MIDI message, onMIDISystemRealtime callback is invoked.
*/
MT32EMU_EXPORT void mt32emu_play_short_message_at(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp);
/** Enqueues a single short MIDI message to be processed ASAP. The message must contain a status byte. */
MT32EMU_EXPORT mt32emu_return_code mt32emu_play_msg(mt32emu_const_context context, mt32emu_bit32u msg);
/** Enqueues a single well formed System Exclusive MIDI message to be processed ASAP. */
MT32EMU_EXPORT mt32emu_return_code mt32emu_play_sysex(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
/** Enqueues a single short MIDI message to play at specified time. The message must contain a status byte. */
MT32EMU_EXPORT mt32emu_return_code mt32emu_play_msg_at(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp);
/** Enqueues a single well formed System Exclusive MIDI message to play at specified time. */
MT32EMU_EXPORT mt32emu_return_code mt32emu_play_sysex_at(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp);
/* WARNING:
* The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
* and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent.
* A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering.
*/
/**
* Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
* See the WARNING above.
*/
MT32EMU_EXPORT void mt32emu_play_msg_now(mt32emu_const_context context, mt32emu_bit32u msg);
/**
* Sends unpacked short MIDI message to the synth for immediate playback. The message must contain a status byte.
* See the WARNING above.
*/
MT32EMU_EXPORT void mt32emu_play_msg_on_part(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity);
/**
* Sends a single well formed System Exclusive MIDI message for immediate processing. The length is in bytes.
* See the WARNING above.
*/
MT32EMU_EXPORT void mt32emu_play_sysex_now(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
/**
* Sends inner body of a System Exclusive MIDI message for direct processing. The length is in bytes.
* See the WARNING above.
*/
MT32EMU_EXPORT void mt32emu_write_sysex(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len);
/** Allows to disable wet reverb output altogether. */
MT32EMU_EXPORT void mt32emu_set_reverb_enabled(mt32emu_const_context context, const mt32emu_boolean reverb_enabled);
/** Returns whether wet reverb output is enabled. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reverb_enabled(mt32emu_const_context context);
/**
* Sets override reverb mode. In this mode, emulation ignores sysexes (or the related part of them) which control the reverb parameters.
* This mode is in effect until it is turned off. When the synth is re-opened, the override mode is unchanged but the state
* of the reverb model is reset to default.
*/
MT32EMU_EXPORT void mt32emu_set_reverb_overridden(mt32emu_const_context context, const mt32emu_boolean reverb_overridden);
/** Returns whether reverb settings are overridden. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reverb_overridden(mt32emu_const_context context);
/**
* Forces reverb model compatibility mode. By default, the compatibility mode corresponds to the used control ROM version.
* Invoking this method with the argument set to true forces emulation of old MT-32 reverb circuit.
* When the argument is false, emulation of the reverb circuit used in new generation of MT-32 compatible modules is enforced
* (these include CM-32L and LAPC-I).
*/
MT32EMU_EXPORT void mt32emu_set_reverb_compatibility_mode(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode);
/** Returns whether reverb is in old MT-32 compatibility mode. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_mt32_reverb_compatibility_mode(mt32emu_const_context context);
/** Returns whether default reverb compatibility mode is the old MT-32 compatibility mode. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_default_reverb_mt32_compatible(mt32emu_const_context context);
/** Sets new DAC input mode. See mt32emu_dac_input_mode for details. */
MT32EMU_EXPORT void mt32emu_set_dac_input_mode(mt32emu_const_context context, const mt32emu_dac_input_mode mode);
/** Returns current DAC input mode. See mt32emu_dac_input_mode for details. */
MT32EMU_EXPORT mt32emu_dac_input_mode mt32emu_get_dac_input_mode(mt32emu_const_context context);
/** Sets new MIDI delay mode. See mt32emu_midi_delay_mode for details. */
MT32EMU_EXPORT void mt32emu_set_midi_delay_mode(mt32emu_const_context context, const mt32emu_midi_delay_mode mode);
/** Returns current MIDI delay mode. See mt32emu_midi_delay_mode for details. */
MT32EMU_EXPORT mt32emu_midi_delay_mode mt32emu_get_midi_delay_mode(mt32emu_const_context context);
/**
* Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
* it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with mt32emu_set_reverb_output_gain()
* it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
* Ignored in MT32EMU_DAC_PURE mode.
*/
MT32EMU_EXPORT void mt32emu_set_output_gain(mt32emu_const_context context, float gain);
/** Returns current output gain factor for synth output channels. */
MT32EMU_EXPORT float mt32emu_get_output_gain(mt32emu_const_context context);
/**
* Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
* analog circuitry of the hardware units. However, together with mt32emu_set_output_gain() it offers to the user a capability
* to control the gain of reverb and non-reverb output channels independently.
*
* Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely
* corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
* there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
* of that for LA32 analogue output. This factor is applied to the reverb output gain.
* Ignored in MT32EMU_DAC_PURE mode.
*/
MT32EMU_EXPORT void mt32emu_set_reverb_output_gain(mt32emu_const_context context, float gain);
/** Returns current output gain factor for reverb wet output channels. */
MT32EMU_EXPORT float mt32emu_get_reverb_output_gain(mt32emu_const_context context);
/** Swaps left and right output channels. */
MT32EMU_EXPORT void mt32emu_set_reversed_stereo_enabled(mt32emu_const_context context, const mt32emu_boolean enabled);
/** Returns whether left and right output channels are swapped. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_reversed_stereo_enabled(mt32emu_const_context context);
/**
* Renders samples to the specified output stream as if they were sampled at the analog stereo output at the desired sample rate.
* If the output sample rate is not specified explicitly, the default output sample rate is used which depends on the current
* mode of analog circuitry emulation. See mt32emu_analog_output_mode.
* The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). Uses NATIVE byte ordering.
*/
MT32EMU_EXPORT void mt32emu_render_bit16s(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len);
/** Same as above but outputs to a float stereo stream. */
MT32EMU_EXPORT void mt32emu_render_float(mt32emu_const_context context, float *stream, mt32emu_bit32u len);
/**
* Renders samples to the specified output streams as if they appeared at the DAC entrance.
* No further processing performed in analog circuitry emulation is applied to the signal.
* NULL may be specified in place of any or all of the stream buffers to skip it.
* The length is in samples, not bytes. Uses NATIVE byte ordering.
*/
MT32EMU_EXPORT void mt32emu_render_bit16s_streams(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len);
/** Same as above but outputs to float streams. */
MT32EMU_EXPORT void mt32emu_render_float_streams(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len);
/** Returns true when there is at least one active partial, otherwise false. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_has_active_partials(mt32emu_const_context context);
/** Returns true if mt32emu_has_active_partials() returns true, or reverb is (somewhat unreliably) detected as being active. */
MT32EMU_EXPORT mt32emu_boolean mt32emu_is_active(mt32emu_const_context context);
/** Returns the maximum number of partials playing simultaneously. */
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_partial_count(mt32emu_const_context context);
/**
* Returns current states of all the parts as a bit set. The least significant bit corresponds to the state of part 1,
* total of 9 bits hold the states of all the parts. If the returned bit for a part is set, there is at least one active
* non-releasing partial playing on this part. This info is useful in emulating behaviour of LCD display of the hardware units.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_part_states(mt32emu_const_context context);
/**
* Fills in current states of all the partials into the array provided. Each byte in the array holds states of 4 partials
* starting from the least significant bits. The state of each partial is packed in a pair of bits.
* The array must be large enough to accommodate states of all the partials.
* @see getPartialCount()
*/
MT32EMU_EXPORT void mt32emu_get_partial_states(mt32emu_const_context context, mt32emu_bit8u *partial_states);
/**
* Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
* to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials.
* Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
* Returns the number of currently playing notes on the specified part.
*/
MT32EMU_EXPORT mt32emu_bit32u mt32emu_get_playing_notes(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities);
/**
* Returns name of the patch set on the specified part.
* Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
*/
MT32EMU_EXPORT const char *mt32emu_get_patch_name(mt32emu_const_context context, mt32emu_bit8u part_number);
/** Stores internal state of emulated synth into an array provided (as it would be acquired from hardware). */
MT32EMU_EXPORT void mt32emu_read_memory(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* #ifndef MT32EMU_C_INTERFACE_H */

View File

@@ -0,0 +1,322 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_C_TYPES_H
#define MT32EMU_C_TYPES_H
#include <stdarg.h>
#include <stddef.h>
#include "../globals.h"
#define MT32EMU_C_ENUMERATIONS
#include "../Enumerations.h"
#undef MT32EMU_C_ENUMERATIONS
typedef unsigned int mt32emu_bit32u;
typedef signed int mt32emu_bit32s;
typedef unsigned short int mt32emu_bit16u;
typedef signed short int mt32emu_bit16s;
typedef unsigned char mt32emu_bit8u;
typedef signed char mt32emu_bit8s;
typedef char mt32emu_sha1_digest[41];
typedef enum {
MT32EMU_BOOL_FALSE, MT32EMU_BOOL_TRUE
} mt32emu_boolean;
typedef enum {
/* Operation completed normally. */
MT32EMU_RC_OK = 0,
MT32EMU_RC_ADDED_CONTROL_ROM = 1,
MT32EMU_RC_ADDED_PCM_ROM = 2,
/* Definite error occurred. */
MT32EMU_RC_ROM_NOT_IDENTIFIED = -1,
MT32EMU_RC_FILE_NOT_FOUND = -2,
MT32EMU_RC_FILE_NOT_LOADED = -3,
MT32EMU_RC_MISSING_ROMS = -4,
MT32EMU_RC_NOT_OPENED = -5,
MT32EMU_RC_QUEUE_FULL = -6,
/* Undefined error occurred. */
MT32EMU_RC_FAILED = -100
} mt32emu_return_code;
/** Emulation context */
typedef struct mt32emu_data *mt32emu_context;
typedef const struct mt32emu_data *mt32emu_const_context;
/* Convenience aliases */
#ifndef __cplusplus
typedef enum mt32emu_analog_output_mode mt32emu_analog_output_mode;
typedef enum mt32emu_dac_input_mode mt32emu_dac_input_mode;
typedef enum mt32emu_midi_delay_mode mt32emu_midi_delay_mode;
typedef enum mt32emu_partial_state mt32emu_partial_state;
typedef enum mt32emu_samplerate_conversion_quality mt32emu_samplerate_conversion_quality;
typedef enum mt32emu_renderer_type mt32emu_renderer_type;
#endif
/** Contains identifiers and descriptions of ROM files being used. */
typedef struct {
const char *control_rom_id;
const char *control_rom_description;
const char *control_rom_sha1_digest;
const char *pcm_rom_id;
const char *pcm_rom_description;
const char *pcm_rom_sha1_digest;
} mt32emu_rom_info;
/** Set of multiplexed output bit16s streams appeared at the DAC entrance. */
typedef struct {
mt32emu_bit16s *nonReverbLeft;
mt32emu_bit16s *nonReverbRight;
mt32emu_bit16s *reverbDryLeft;
mt32emu_bit16s *reverbDryRight;
mt32emu_bit16s *reverbWetLeft;
mt32emu_bit16s *reverbWetRight;
} mt32emu_dac_output_bit16s_streams;
/** Set of multiplexed output float streams appeared at the DAC entrance. */
typedef struct {
float *nonReverbLeft;
float *nonReverbRight;
float *reverbDryLeft;
float *reverbDryRight;
float *reverbWetLeft;
float *reverbWetRight;
} mt32emu_dac_output_float_streams;
/* === Interface handling === */
/** Report handler interface versions */
typedef enum {
MT32EMU_REPORT_HANDLER_VERSION_0 = 0,
MT32EMU_REPORT_HANDLER_VERSION_CURRENT = MT32EMU_REPORT_HANDLER_VERSION_0
} mt32emu_report_handler_version;
/** MIDI receiver interface versions */
typedef enum {
MT32EMU_MIDI_RECEIVER_VERSION_0 = 0,
MT32EMU_MIDI_RECEIVER_VERSION_CURRENT = MT32EMU_MIDI_RECEIVER_VERSION_0
} mt32emu_midi_receiver_version;
/** Synth interface versions */
typedef enum {
MT32EMU_SERVICE_VERSION_0 = 0,
MT32EMU_SERVICE_VERSION_1 = 1,
MT32EMU_SERVICE_VERSION_CURRENT = MT32EMU_SERVICE_VERSION_1
} mt32emu_service_version;
/* === Report Handler Interface === */
typedef union mt32emu_report_handler_i mt32emu_report_handler_i;
/** Interface for handling reported events (initial version) */
typedef struct {
/** Returns the actual interface version ID */
mt32emu_report_handler_version (*getVersionID)(mt32emu_report_handler_i i);
/** Callback for debug messages, in vprintf() format */
void (*printDebug)(void *instance_data, const char *fmt, va_list list);
/** Callbacks for reporting errors */
void (*onErrorControlROM)(void *instance_data);
void (*onErrorPCMROM)(void *instance_data);
/** Callback for reporting about displaying a new custom message on LCD */
void (*showLCDMessage)(void *instance_data, const char *message);
/** Callback for reporting actual processing of a MIDI message */
void (*onMIDIMessagePlayed)(void *instance_data);
/**
* Callback for reporting an overflow of the input MIDI queue.
* Returns MT32EMU_BOOL_TRUE if a recovery action was taken
* and yet another attempt to enqueue the MIDI event is desired.
*/
mt32emu_boolean (*onMIDIQueueOverflow)(void *instance_data);
/**
* Callback invoked when a System Realtime MIDI message is detected in functions
* mt32emu_parse_stream and mt32emu_play_short_message and the likes.
*/
void (*onMIDISystemRealtime)(void *instance_data, mt32emu_bit8u system_realtime);
/** Callbacks for reporting system events */
void (*onDeviceReset)(void *instance_data);
void (*onDeviceReconfig)(void *instance_data);
/** Callbacks for reporting changes of reverb settings */
void (*onNewReverbMode)(void *instance_data, mt32emu_bit8u mode);
void (*onNewReverbTime)(void *instance_data, mt32emu_bit8u time);
void (*onNewReverbLevel)(void *instance_data, mt32emu_bit8u level);
/** Callbacks for reporting various information */
void (*onPolyStateChanged)(void *instance_data, mt32emu_bit8u part_num);
void (*onProgramChanged)(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name);
} mt32emu_report_handler_i_v0;
/**
* Extensible interface for handling reported events.
* Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
* It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method.
*/
union mt32emu_report_handler_i {
const mt32emu_report_handler_i_v0 *v0;
};
/* === MIDI Receiver Interface === */
typedef union mt32emu_midi_receiver_i mt32emu_midi_receiver_i;
/** Interface for receiving MIDI messages generated by MIDI stream parser (initial version) */
typedef struct {
/** Returns the actual interface version ID */
mt32emu_midi_receiver_version (*getVersionID)(mt32emu_midi_receiver_i i);
/** Invoked when a complete short MIDI message is parsed in the input MIDI stream. */
void (*handleShortMessage)(void *instance_data, const mt32emu_bit32u message);
/** Invoked when a complete well-formed System Exclusive MIDI message is parsed in the input MIDI stream. */
void (*handleSysex)(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length);
/** Invoked when a System Realtime MIDI message is parsed in the input MIDI stream. */
void (*handleSystemRealtimeMessage)(void *instance_data, const mt32emu_bit8u realtime);
} mt32emu_midi_receiver_i_v0;
/**
* Extensible interface for receiving MIDI messages.
* Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
* It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method.
*/
union mt32emu_midi_receiver_i {
const mt32emu_midi_receiver_i_v0 *v0;
};
/* === Service Interface === */
typedef union mt32emu_service_i mt32emu_service_i;
/**
* Basic interface that defines all the library services (initial version).
* The members closely resemble C functions declared in c_interface.h, and the intention is to provide for easier
* access when the library is dynamically loaded in run-time, e.g. as a plugin. This way the client only needs
* to bind to mt32emu_get_service_i() function instead of binding to each function it needs to use.
* See c_interface.h for parameter description.
*/
#define MT32EMU_SERVICE_I_V0 \
/** Returns the actual interface version ID */ \
mt32emu_service_version (*getVersionID)(mt32emu_service_i i); \
mt32emu_report_handler_version (*getSupportedReportHandlerVersionID)(); \
mt32emu_midi_receiver_version (*getSupportedMIDIReceiverVersionID)(); \
\
mt32emu_bit32u (*getLibraryVersionInt)(); \
const char *(*getLibraryVersionString)(); \
\
mt32emu_bit32u (*getStereoOutputSamplerate)(const mt32emu_analog_output_mode analog_output_mode); \
\
mt32emu_context (*createContext)(mt32emu_report_handler_i report_handler, void *instance_data); \
void (*freeContext)(mt32emu_context context); \
mt32emu_return_code (*addROMData)(mt32emu_context context, const mt32emu_bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest); \
mt32emu_return_code (*addROMFile)(mt32emu_context context, const char *filename); \
void (*getROMInfo)(mt32emu_const_context context, mt32emu_rom_info *rom_info); \
void (*setPartialCount)(mt32emu_context context, const mt32emu_bit32u partial_count); \
void (*setAnalogOutputMode)(mt32emu_context context, const mt32emu_analog_output_mode analog_output_mode); \
mt32emu_return_code (*openSynth)(mt32emu_const_context context); \
void (*closeSynth)(mt32emu_const_context context); \
mt32emu_boolean (*isOpen)(mt32emu_const_context context); \
mt32emu_bit32u (*getActualStereoOutputSamplerate)(mt32emu_const_context context); \
void (*flushMIDIQueue)(mt32emu_const_context context); \
mt32emu_bit32u (*setMIDIEventQueueSize)(mt32emu_const_context context, const mt32emu_bit32u queue_size); \
void (*setMIDIReceiver)(mt32emu_context context, mt32emu_midi_receiver_i midi_receiver, void *instance_data); \
\
void (*parseStream)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length); \
void (*parseStream_At)(mt32emu_const_context context, const mt32emu_bit8u *stream, mt32emu_bit32u length, mt32emu_bit32u timestamp); \
void (*playShortMessage)(mt32emu_const_context context, mt32emu_bit32u message); \
void (*playShortMessageAt)(mt32emu_const_context context, mt32emu_bit32u message, mt32emu_bit32u timestamp); \
mt32emu_return_code (*playMsg)(mt32emu_const_context context, mt32emu_bit32u msg); \
mt32emu_return_code (*playSysex)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
mt32emu_return_code (*playMsgAt)(mt32emu_const_context context, mt32emu_bit32u msg, mt32emu_bit32u timestamp); \
mt32emu_return_code (*playSysexAt)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len, mt32emu_bit32u timestamp); \
\
void (*playMsgNow)(mt32emu_const_context context, mt32emu_bit32u msg); \
void (*playMsgOnPart)(mt32emu_const_context context, mt32emu_bit8u part, mt32emu_bit8u code, mt32emu_bit8u note, mt32emu_bit8u velocity); \
void (*playSysexNow)(mt32emu_const_context context, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
void (*writeSysex)(mt32emu_const_context context, mt32emu_bit8u channel, const mt32emu_bit8u *sysex, mt32emu_bit32u len); \
\
void (*setReverbEnabled)(mt32emu_const_context context, const mt32emu_boolean reverb_enabled); \
mt32emu_boolean (*isReverbEnabled)(mt32emu_const_context context); \
void (*setReverbOverridden)(mt32emu_const_context context, const mt32emu_boolean reverb_overridden); \
mt32emu_boolean (*isReverbOverridden)(mt32emu_const_context context); \
void (*setReverbCompatibilityMode)(mt32emu_const_context context, const mt32emu_boolean mt32_compatible_mode); \
mt32emu_boolean (*isMT32ReverbCompatibilityMode)(mt32emu_const_context context); \
mt32emu_boolean (*isDefaultReverbMT32Compatible)(mt32emu_const_context context); \
\
void (*setDACInputMode)(mt32emu_const_context context, const mt32emu_dac_input_mode mode); \
mt32emu_dac_input_mode (*getDACInputMode)(mt32emu_const_context context); \
\
void (*setMIDIDelayMode)(mt32emu_const_context context, const mt32emu_midi_delay_mode mode); \
mt32emu_midi_delay_mode (*getMIDIDelayMode)(mt32emu_const_context context); \
\
void (*setOutputGain)(mt32emu_const_context context, float gain); \
float (*getOutputGain)(mt32emu_const_context context); \
void (*setReverbOutputGain)(mt32emu_const_context context, float gain); \
float (*getReverbOutputGain)(mt32emu_const_context context); \
\
void (*setReversedStereoEnabled)(mt32emu_const_context context, const mt32emu_boolean enabled); \
mt32emu_boolean (*isReversedStereoEnabled)(mt32emu_const_context context); \
\
void (*renderBit16s)(mt32emu_const_context context, mt32emu_bit16s *stream, mt32emu_bit32u len); \
void (*renderFloat)(mt32emu_const_context context, float *stream, mt32emu_bit32u len); \
void (*renderBit16sStreams)(mt32emu_const_context context, const mt32emu_dac_output_bit16s_streams *streams, mt32emu_bit32u len); \
void (*renderFloatStreams)(mt32emu_const_context context, const mt32emu_dac_output_float_streams *streams, mt32emu_bit32u len); \
\
mt32emu_boolean (*hasActivePartials)(mt32emu_const_context context); \
mt32emu_boolean (*isActive)(mt32emu_const_context context); \
mt32emu_bit32u (*getPartialCount)(mt32emu_const_context context); \
mt32emu_bit32u (*getPartStates)(mt32emu_const_context context); \
void (*getPartialStates)(mt32emu_const_context context, mt32emu_bit8u *partial_states); \
mt32emu_bit32u (*getPlayingNotes)(mt32emu_const_context context, mt32emu_bit8u part_number, mt32emu_bit8u *keys, mt32emu_bit8u *velocities); \
const char *(*getPatchName)(mt32emu_const_context context, mt32emu_bit8u part_number); \
void (*readMemory)(mt32emu_const_context context, mt32emu_bit32u addr, mt32emu_bit32u len, mt32emu_bit8u *data);
#define MT32EMU_SERVICE_I_V1 \
mt32emu_analog_output_mode (*getBestAnalogOutputMode)(const double target_samplerate); \
void (*setStereoOutputSampleRate)(mt32emu_context context, const double samplerate); \
void (*setSamplerateConversionQuality)(mt32emu_context context, const mt32emu_samplerate_conversion_quality quality); \
void (*selectRendererType)(mt32emu_context context, mt32emu_renderer_type renderer_type); \
mt32emu_renderer_type (*getSelectedRendererType)(mt32emu_context context); \
mt32emu_bit32u (*convertOutputToSynthTimestamp)(mt32emu_const_context context, mt32emu_bit32u output_timestamp); \
mt32emu_bit32u (*convertSynthToOutputTimestamp)(mt32emu_const_context context, mt32emu_bit32u synth_timestamp);
typedef struct {
MT32EMU_SERVICE_I_V0
} mt32emu_service_i_v0;
typedef struct {
MT32EMU_SERVICE_I_V0
MT32EMU_SERVICE_I_V1
} mt32emu_service_i_v1;
/**
* Extensible interface for all the library services.
* Union intended to view an interface of any subsequent version as any parent interface not requiring a cast.
* It is caller's responsibility to check the actual interface version in runtime using the getVersionID() method.
*/
union mt32emu_service_i {
const mt32emu_service_i_v0 *v0;
const mt32emu_service_i_v1 *v1;
};
#undef MT32EMU_SERVICE_I_V0
#undef MT32EMU_SERVICE_I_V1
#endif /* #ifndef MT32EMU_C_TYPES_H */

View File

@@ -0,0 +1,468 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_CPP_INTERFACE_H
#define MT32EMU_CPP_INTERFACE_H
#include <cstdarg>
#include "../globals.h"
#include "c_types.h"
#include "../Types.h"
#include "../Enumerations.h"
#if MT32EMU_API_TYPE == 2
extern "C" {
/** Returns mt32emu_service_i interface. */
mt32emu_service_i mt32emu_get_service_i();
}
#define mt32emu_get_supported_report_handler_version i.v0->getSupportedReportHandlerVersionID
#define mt32emu_get_supported_midi_receiver_version i.v0->getSupportedMIDIReceiverVersionID
#define mt32emu_get_library_version_int i.v0->getLibraryVersionInt
#define mt32emu_get_library_version_string i.v0->getLibraryVersionString
#define mt32emu_get_stereo_output_samplerate i.v0->getStereoOutputSamplerate
#define mt32emu_get_best_analog_output_mode iV1()->getBestAnalogOutputMode
#define mt32emu_create_context i.v0->createContext
#define mt32emu_free_context i.v0->freeContext
#define mt32emu_add_rom_data i.v0->addROMData
#define mt32emu_add_rom_file i.v0->addROMFile
#define mt32emu_get_rom_info i.v0->getROMInfo
#define mt32emu_set_partial_count i.v0->setPartialCount
#define mt32emu_set_analog_output_mode i.v0->setAnalogOutputMode
#define mt32emu_set_stereo_output_samplerate iV1()->setStereoOutputSampleRate
#define mt32emu_set_samplerate_conversion_quality iV1()->setSamplerateConversionQuality
#define mt32emu_select_renderer_type iV1()->selectRendererType
#define mt32emu_get_selected_renderer_type iV1()->getSelectedRendererType
#define mt32emu_open_synth i.v0->openSynth
#define mt32emu_close_synth i.v0->closeSynth
#define mt32emu_is_open i.v0->isOpen
#define mt32emu_get_actual_stereo_output_samplerate i.v0->getActualStereoOutputSamplerate
#define mt32emu_convert_output_to_synth_timestamp iV1()->convertOutputToSynthTimestamp
#define mt32emu_convert_synth_to_output_timestamp iV1()->convertSynthToOutputTimestamp
#define mt32emu_flush_midi_queue i.v0->flushMIDIQueue
#define mt32emu_set_midi_event_queue_size i.v0->setMIDIEventQueueSize
#define mt32emu_set_midi_receiver i.v0->setMIDIReceiver
#define mt32emu_parse_stream i.v0->parseStream
#define mt32emu_parse_stream_at i.v0->parseStream_At
#define mt32emu_play_short_message i.v0->playShortMessage
#define mt32emu_play_short_message_at i.v0->playShortMessageAt
#define mt32emu_play_msg i.v0->playMsg
#define mt32emu_play_sysex i.v0->playSysex
#define mt32emu_play_msg_at i.v0->playMsgAt
#define mt32emu_play_sysex_at i.v0->playSysexAt
#define mt32emu_play_msg_now i.v0->playMsgNow
#define mt32emu_play_msg_on_part i.v0->playMsgOnPart
#define mt32emu_play_sysex_now i.v0->playSysexNow
#define mt32emu_write_sysex i.v0->writeSysex
#define mt32emu_set_reverb_enabled i.v0->setReverbEnabled
#define mt32emu_is_reverb_enabled i.v0->isReverbEnabled
#define mt32emu_set_reverb_overridden i.v0->setReverbOverridden
#define mt32emu_is_reverb_overridden i.v0->isReverbOverridden
#define mt32emu_set_reverb_compatibility_mode i.v0->setReverbCompatibilityMode
#define mt32emu_is_mt32_reverb_compatibility_mode i.v0->isMT32ReverbCompatibilityMode
#define mt32emu_is_default_reverb_mt32_compatible i.v0->isDefaultReverbMT32Compatible
#define mt32emu_set_dac_input_mode i.v0->setDACInputMode
#define mt32emu_get_dac_input_mode i.v0->getDACInputMode
#define mt32emu_set_midi_delay_mode i.v0->setMIDIDelayMode
#define mt32emu_get_midi_delay_mode i.v0->getMIDIDelayMode
#define mt32emu_set_output_gain i.v0->setOutputGain
#define mt32emu_get_output_gain i.v0->getOutputGain
#define mt32emu_set_reverb_output_gain i.v0->setReverbOutputGain
#define mt32emu_get_reverb_output_gain i.v0->getReverbOutputGain
#define mt32emu_set_reversed_stereo_enabled i.v0->setReversedStereoEnabled
#define mt32emu_is_reversed_stereo_enabled i.v0->isReversedStereoEnabled
#define mt32emu_render_bit16s i.v0->renderBit16s
#define mt32emu_render_float i.v0->renderFloat
#define mt32emu_render_bit16s_streams i.v0->renderBit16sStreams
#define mt32emu_render_float_streams i.v0->renderFloatStreams
#define mt32emu_has_active_partials i.v0->hasActivePartials
#define mt32emu_is_active i.v0->isActive
#define mt32emu_get_partial_count i.v0->getPartialCount
#define mt32emu_get_part_states i.v0->getPartStates
#define mt32emu_get_partial_states i.v0->getPartialStates
#define mt32emu_get_playing_notes i.v0->getPlayingNotes
#define mt32emu_get_patch_name i.v0->getPatchName
#define mt32emu_read_memory i.v0->readMemory
#else // #if MT32EMU_API_TYPE == 2
#include "c_interface.h"
#endif // #if MT32EMU_API_TYPE == 2
namespace MT32Emu {
namespace CppInterfaceImpl {
static const mt32emu_report_handler_i NULL_REPORT_HANDLER = { NULL };
static mt32emu_report_handler_i getReportHandlerThunk();
static mt32emu_midi_receiver_i getMidiReceiverThunk();
}
/*
* The classes below correspond to the interfaces defined in c_types.h and provided for convenience when using C++.
* The approach used makes no assumption of any internal class data memory layout, since the C++ standard does not
* provide any detail in this area and leaves it up to the implementation. Therefore, this way portability is guaranteed,
* despite the implementation may be a little inefficient.
* See c_types.h and c_interface.h for description of the corresponding interface methods.
*/
// Defines the interface for handling reported events.
// Corresponds to the current version of mt32emu_report_handler_i interface.
class IReportHandler {
public:
virtual void printDebug(const char *fmt, va_list list) = 0;
virtual void onErrorControlROM() = 0;
virtual void onErrorPCMROM() = 0;
virtual void showLCDMessage(const char *message) = 0;
virtual void onMIDIMessagePlayed() = 0;
virtual bool onMIDIQueueOverflow() = 0;
virtual void onMIDISystemRealtime(Bit8u system_realtime) = 0;
virtual void onDeviceReset() = 0;
virtual void onDeviceReconfig() = 0;
virtual void onNewReverbMode(Bit8u mode) = 0;
virtual void onNewReverbTime(Bit8u time) = 0;
virtual void onNewReverbLevel(Bit8u level) = 0;
virtual void onPolyStateChanged(Bit8u part_num) = 0;
virtual void onProgramChanged(Bit8u part_num, const char *sound_group_name, const char *patch_name) = 0;
protected:
~IReportHandler() {}
};
// Defines the interface for receiving MIDI messages generated by MIDI stream parser.
// Corresponds to the current version of mt32emu_midi_receiver_i interface.
class IMidiReceiver {
public:
virtual void handleShortMessage(const Bit32u message) = 0;
virtual void handleSysex(const Bit8u stream[], const Bit32u length) = 0;
virtual void handleSystemRealtimeMessage(const Bit8u realtime) = 0;
protected:
~IMidiReceiver() {}
};
// Defines all the library services.
// Corresponds to the current version of mt32emu_service_i interface.
class Service {
public:
#if MT32EMU_API_TYPE == 2
explicit Service(mt32emu_service_i interface, mt32emu_context context = NULL) : i(interface), c(context) {}
#else
explicit Service(mt32emu_context context = NULL) : c(context) {}
#endif
~Service() { if (c != NULL) mt32emu_free_context(c); }
// Context-independent methods
#if MT32EMU_API_TYPE == 2
mt32emu_service_version getVersionID() { return i.v0->getVersionID(i); }
#endif
mt32emu_report_handler_version getSupportedReportHandlerVersionID() { return mt32emu_get_supported_report_handler_version(); }
mt32emu_midi_receiver_version getSupportedMIDIReceiverVersionID() { return mt32emu_get_supported_midi_receiver_version(); }
Bit32u getLibraryVersionInt() { return mt32emu_get_library_version_int(); }
const char *getLibraryVersionString() { return mt32emu_get_library_version_string(); }
Bit32u getStereoOutputSamplerate(const AnalogOutputMode analog_output_mode) { return mt32emu_get_stereo_output_samplerate(static_cast<mt32emu_analog_output_mode>(analog_output_mode)); }
AnalogOutputMode getBestAnalogOutputMode(const double target_samplerate) { return static_cast<AnalogOutputMode>(mt32emu_get_best_analog_output_mode(target_samplerate)); }
// Context-dependent methods
mt32emu_context getContext() { return c; }
void createContext(mt32emu_report_handler_i report_handler = CppInterfaceImpl::NULL_REPORT_HANDLER, void *instance_data = NULL) { freeContext(); c = mt32emu_create_context(report_handler, instance_data); }
void createContext(IReportHandler &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(), &report_handler); }
void freeContext() { if (c != NULL) { mt32emu_free_context(c); c = NULL; } }
mt32emu_return_code addROMData(const Bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest = NULL) { return mt32emu_add_rom_data(c, data, data_size, sha1_digest); }
mt32emu_return_code addROMFile(const char *filename) { return mt32emu_add_rom_file(c, filename); }
void getROMInfo(mt32emu_rom_info *rom_info) { mt32emu_get_rom_info(c, rom_info); }
void setPartialCount(const Bit32u partial_count) { mt32emu_set_partial_count(c, partial_count); }
void setAnalogOutputMode(const AnalogOutputMode analog_output_mode) { mt32emu_set_analog_output_mode(c, static_cast<mt32emu_analog_output_mode>(analog_output_mode)); }
void setStereoOutputSampleRate(const double samplerate) { mt32emu_set_stereo_output_samplerate(c, samplerate); }
void setSamplerateConversionQuality(const SamplerateConversionQuality quality) { mt32emu_set_samplerate_conversion_quality(c, static_cast<mt32emu_samplerate_conversion_quality>(quality)); }
void selectRendererType(const RendererType newRendererType) { mt32emu_select_renderer_type(c, static_cast<mt32emu_renderer_type>(newRendererType)); }
RendererType getSelectedRendererType() { return static_cast<RendererType>(mt32emu_get_selected_renderer_type(c)); }
mt32emu_return_code openSynth() { return mt32emu_open_synth(c); }
void closeSynth() { mt32emu_close_synth(c); }
bool isOpen() { return mt32emu_is_open(c) != MT32EMU_BOOL_FALSE; }
Bit32u getActualStereoOutputSamplerate() { return mt32emu_get_actual_stereo_output_samplerate(c); }
Bit32u convertOutputToSynthTimestamp(Bit32u output_timestamp) { return mt32emu_convert_output_to_synth_timestamp(c, output_timestamp); }
Bit32u convertSynthToOutputTimestamp(Bit32u synth_timestamp) { return mt32emu_convert_synth_to_output_timestamp(c, synth_timestamp); }
void flushMIDIQueue() { mt32emu_flush_midi_queue(c); }
Bit32u setMIDIEventQueueSize(const Bit32u queue_size) { return mt32emu_set_midi_event_queue_size(c, queue_size); }
void setMIDIReceiver(mt32emu_midi_receiver_i midi_receiver, void *instance_data) { mt32emu_set_midi_receiver(c, midi_receiver, instance_data); }
void setMIDIReceiver(IMidiReceiver &midi_receiver) { setMIDIReceiver(CppInterfaceImpl::getMidiReceiverThunk(), &midi_receiver); }
void parseStream(const Bit8u *stream, Bit32u length) { mt32emu_parse_stream(c, stream, length); }
void parseStream_At(const Bit8u *stream, Bit32u length, Bit32u timestamp) { mt32emu_parse_stream_at(c, stream, length, timestamp); }
void playShortMessage(Bit32u message) { mt32emu_play_short_message(c, message); }
void playShortMessageAt(Bit32u message, Bit32u timestamp) { mt32emu_play_short_message_at(c, message, timestamp); }
mt32emu_return_code playMsg(Bit32u msg) { return mt32emu_play_msg(c, msg); }
mt32emu_return_code playSysex(const Bit8u *sysex, Bit32u len) { return mt32emu_play_sysex(c, sysex, len); }
mt32emu_return_code playMsgAt(Bit32u msg, Bit32u timestamp) { return mt32emu_play_msg_at(c, msg, timestamp); }
mt32emu_return_code playSysexAt(const Bit8u *sysex, Bit32u len, Bit32u timestamp) { return mt32emu_play_sysex_at(c, sysex, len, timestamp); }
void playMsgNow(Bit32u msg) { mt32emu_play_msg_now(c, msg); }
void playMsgOnPart(Bit8u part, Bit8u code, Bit8u note, Bit8u velocity) { mt32emu_play_msg_on_part(c, part, code, note, velocity); }
void playSysexNow(const Bit8u *sysex, Bit32u len) { mt32emu_play_sysex_now(c, sysex, len); }
void writeSysex(Bit8u channel, const Bit8u *sysex, Bit32u len) { mt32emu_write_sysex(c, channel, sysex, len); }
void setReverbEnabled(const bool reverb_enabled) { mt32emu_set_reverb_enabled(c, reverb_enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isReverbEnabled() { return mt32emu_is_reverb_enabled(c) != MT32EMU_BOOL_FALSE; }
void setReverbOverridden(const bool reverb_overridden) { mt32emu_set_reverb_overridden(c, reverb_overridden ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isReverbOverridden() { return mt32emu_is_reverb_overridden(c) != MT32EMU_BOOL_FALSE; }
void setReverbCompatibilityMode(const bool mt32_compatible_mode) { mt32emu_set_reverb_compatibility_mode(c, mt32_compatible_mode ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isMT32ReverbCompatibilityMode() { return mt32emu_is_mt32_reverb_compatibility_mode(c) != MT32EMU_BOOL_FALSE; }
bool isDefaultReverbMT32Compatible() { return mt32emu_is_default_reverb_mt32_compatible(c) != MT32EMU_BOOL_FALSE; }
void setDACInputMode(const DACInputMode mode) { mt32emu_set_dac_input_mode(c, static_cast<mt32emu_dac_input_mode>(mode)); }
DACInputMode getDACInputMode() { return static_cast<DACInputMode>(mt32emu_get_dac_input_mode(c)); }
void setMIDIDelayMode(const MIDIDelayMode mode) { mt32emu_set_midi_delay_mode(c, static_cast<mt32emu_midi_delay_mode>(mode)); }
MIDIDelayMode getMIDIDelayMode() { return static_cast<MIDIDelayMode>(mt32emu_get_midi_delay_mode(c)); }
void setOutputGain(float gain) { mt32emu_set_output_gain(c, gain); }
float getOutputGain() { return mt32emu_get_output_gain(c); }
void setReverbOutputGain(float gain) { mt32emu_set_reverb_output_gain(c, gain); }
float getReverbOutputGain() { return mt32emu_get_reverb_output_gain(c); }
void setReversedStereoEnabled(const bool enabled) { mt32emu_set_reversed_stereo_enabled(c, enabled ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); }
bool isReversedStereoEnabled() { return mt32emu_is_reversed_stereo_enabled(c) != MT32EMU_BOOL_FALSE; }
void renderBit16s(Bit16s *stream, Bit32u len) { mt32emu_render_bit16s(c, stream, len); }
void renderFloat(float *stream, Bit32u len) { mt32emu_render_float(c, stream, len); }
void renderBit16sStreams(const mt32emu_dac_output_bit16s_streams *streams, Bit32u len) { mt32emu_render_bit16s_streams(c, streams, len); }
void renderFloatStreams(const mt32emu_dac_output_float_streams *streams, Bit32u len) { mt32emu_render_float_streams(c, streams, len); }
bool hasActivePartials() { return mt32emu_has_active_partials(c) != MT32EMU_BOOL_FALSE; }
bool isActive() { return mt32emu_is_active(c) != MT32EMU_BOOL_FALSE; }
Bit32u getPartialCount() { return mt32emu_get_partial_count(c); }
Bit32u getPartStates() { return mt32emu_get_part_states(c); }
void getPartialStates(Bit8u *partial_states) { mt32emu_get_partial_states(c, partial_states); }
Bit32u getPlayingNotes(Bit8u part_number, Bit8u *keys, Bit8u *velocities) { return mt32emu_get_playing_notes(c, part_number, keys, velocities); }
const char *getPatchName(Bit8u part_number) { return mt32emu_get_patch_name(c, part_number); }
void readMemory(Bit32u addr, Bit32u len, Bit8u *data) { mt32emu_read_memory(c, addr, len, data); }
private:
#if MT32EMU_API_TYPE == 2
const mt32emu_service_i i;
#endif
mt32emu_context c;
#if MT32EMU_API_TYPE == 2
const mt32emu_service_i_v1 *iV1() { return (getVersionID() < MT32EMU_SERVICE_VERSION_1) ? NULL : i.v1; }
#endif
};
namespace CppInterfaceImpl {
static mt32emu_report_handler_version getReportHandlerVersionID(mt32emu_report_handler_i) {
return MT32EMU_REPORT_HANDLER_VERSION_CURRENT;
}
static void printDebug(void *instance_data, const char *fmt, va_list list) {
static_cast<IReportHandler *>(instance_data)->printDebug(fmt, list);
}
static void onErrorControlROM(void *instance_data) {
static_cast<IReportHandler *>(instance_data)->onErrorControlROM();
}
static void onErrorPCMROM(void *instance_data) {
static_cast<IReportHandler *>(instance_data)->onErrorPCMROM();
}
static void showLCDMessage(void *instance_data, const char *message) {
static_cast<IReportHandler *>(instance_data)->showLCDMessage(message);
}
static void onMIDIMessagePlayed(void *instance_data) {
static_cast<IReportHandler *>(instance_data)->onMIDIMessagePlayed();
}
static mt32emu_boolean onMIDIQueueOverflow(void *instance_data) {
return static_cast<IReportHandler *>(instance_data)->onMIDIQueueOverflow() ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE;
}
static void onMIDISystemRealtime(void *instance_data, mt32emu_bit8u system_realtime) {
static_cast<IReportHandler *>(instance_data)->onMIDISystemRealtime(system_realtime);
}
static void onDeviceReset(void *instance_data) {
static_cast<IReportHandler *>(instance_data)->onDeviceReset();
}
static void onDeviceReconfig(void *instance_data) {
static_cast<IReportHandler *>(instance_data)->onDeviceReconfig();
}
static void onNewReverbMode(void *instance_data, mt32emu_bit8u mode) {
static_cast<IReportHandler *>(instance_data)->onNewReverbMode(mode);
}
static void onNewReverbTime(void *instance_data, mt32emu_bit8u time) {
static_cast<IReportHandler *>(instance_data)->onNewReverbTime(time);
}
static void onNewReverbLevel(void *instance_data, mt32emu_bit8u level) {
static_cast<IReportHandler *>(instance_data)->onNewReverbLevel(level);
}
static void onPolyStateChanged(void *instance_data, mt32emu_bit8u part_num) {
static_cast<IReportHandler *>(instance_data)->onPolyStateChanged(part_num);
}
static void onProgramChanged(void *instance_data, mt32emu_bit8u part_num, const char *sound_group_name, const char *patch_name) {
static_cast<IReportHandler *>(instance_data)->onProgramChanged(part_num, sound_group_name, patch_name);
}
static mt32emu_report_handler_i getReportHandlerThunk() {
static const mt32emu_report_handler_i_v0 REPORT_HANDLER_V0_THUNK = {
getReportHandlerVersionID,
printDebug,
onErrorControlROM,
onErrorPCMROM,
showLCDMessage,
onMIDIMessagePlayed,
onMIDIQueueOverflow,
onMIDISystemRealtime,
onDeviceReset,
onDeviceReconfig,
onNewReverbMode,
onNewReverbTime,
onNewReverbLevel,
onPolyStateChanged,
onProgramChanged
};
static const mt32emu_report_handler_i REPORT_HANDLER_THUNK = { &REPORT_HANDLER_V0_THUNK };
return REPORT_HANDLER_THUNK;
}
static mt32emu_midi_receiver_version getMidiReceiverVersionID(mt32emu_midi_receiver_i) {
return MT32EMU_MIDI_RECEIVER_VERSION_CURRENT;
}
static void handleShortMessage(void *instance_data, const mt32emu_bit32u message) {
static_cast<IMidiReceiver *>(instance_data)->handleShortMessage(message);
}
static void handleSysex(void *instance_data, const mt32emu_bit8u stream[], const mt32emu_bit32u length) {
static_cast<IMidiReceiver *>(instance_data)->handleSysex(stream, length);
}
static void handleSystemRealtimeMessage(void *instance_data, const mt32emu_bit8u realtime) {
static_cast<IMidiReceiver *>(instance_data)->handleSystemRealtimeMessage(realtime);
}
static mt32emu_midi_receiver_i getMidiReceiverThunk() {
static const mt32emu_midi_receiver_i_v0 MIDI_RECEIVER_V0_THUNK = {
getMidiReceiverVersionID,
handleShortMessage,
handleSysex,
handleSystemRealtimeMessage
};
static const mt32emu_midi_receiver_i MIDI_RECEIVER_THUNK = { &MIDI_RECEIVER_V0_THUNK };
return MIDI_RECEIVER_THUNK;
}
} // namespace CppInterfaceImpl
} // namespace MT32Emu
#if MT32EMU_API_TYPE == 2
#undef mt32emu_get_supported_report_handler_version
#undef mt32emu_get_supported_midi_receiver_version
#undef mt32emu_get_library_version_int
#undef mt32emu_get_library_version_string
#undef mt32emu_get_stereo_output_samplerate
#undef mt32emu_get_best_analog_output_mode
#undef mt32emu_create_context
#undef mt32emu_free_context
#undef mt32emu_add_rom_data
#undef mt32emu_add_rom_file
#undef mt32emu_get_rom_info
#undef mt32emu_set_partial_count
#undef mt32emu_set_analog_output_mode
#undef mt32emu_set_stereo_output_samplerate
#undef mt32emu_set_samplerate_conversion_quality
#undef mt32emu_select_renderer_type
#undef mt32emu_get_selected_renderer_type
#undef mt32emu_open_synth
#undef mt32emu_close_synth
#undef mt32emu_is_open
#undef mt32emu_get_actual_stereo_output_samplerate
#undef mt32emu_convert_output_to_synth_timestamp
#undef mt32emu_convert_synth_to_output_timestamp
#undef mt32emu_flush_midi_queue
#undef mt32emu_set_midi_event_queue_size
#undef mt32emu_set_midi_receiver
#undef mt32emu_parse_stream
#undef mt32emu_parse_stream_at
#undef mt32emu_play_short_message
#undef mt32emu_play_short_message_at
#undef mt32emu_play_msg
#undef mt32emu_play_sysex
#undef mt32emu_play_msg_at
#undef mt32emu_play_sysex_at
#undef mt32emu_play_msg_now
#undef mt32emu_play_msg_on_part
#undef mt32emu_play_sysex_now
#undef mt32emu_write_sysex
#undef mt32emu_set_reverb_enabled
#undef mt32emu_is_reverb_enabled
#undef mt32emu_set_reverb_overridden
#undef mt32emu_is_reverb_overridden
#undef mt32emu_set_reverb_compatibility_mode
#undef mt32emu_is_mt32_reverb_compatibility_mode
#undef mt32emu_is_default_reverb_mt32_compatible
#undef mt32emu_set_dac_input_mode
#undef mt32emu_get_dac_input_mode
#undef mt32emu_set_midi_delay_mode
#undef mt32emu_get_midi_delay_mode
#undef mt32emu_set_output_gain
#undef mt32emu_get_output_gain
#undef mt32emu_set_reverb_output_gain
#undef mt32emu_get_reverb_output_gain
#undef mt32emu_set_reversed_stereo_enabled
#undef mt32emu_is_reversed_stereo_enabled
#undef mt32emu_render_bit16s
#undef mt32emu_render_float
#undef mt32emu_render_bit16s_streams
#undef mt32emu_render_float_streams
#undef mt32emu_has_active_partials
#undef mt32emu_is_active
#undef mt32emu_get_partial_count
#undef mt32emu_get_part_states
#undef mt32emu_get_partial_states
#undef mt32emu_get_playing_notes
#undef mt32emu_get_patch_name
#undef mt32emu_read_memory
#endif // #if MT32EMU_API_TYPE == 2
#endif /* #ifndef MT32EMU_CPP_INTERFACE_H */

38
src/SOUND/munt/config.h Normal file
View File

@@ -0,0 +1,38 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_CONFIG_H
#define MT32EMU_CONFIG_H
#define MT32EMU_VERSION "2.1.0"
#define MT32EMU_VERSION_MAJOR 2
#define MT32EMU_VERSION_MINOR 1
#define MT32EMU_VERSION_PATCH 0
/* Library Exports Configuration
*
* This reflects the API types actually provided by the library build.
* 0: The full-featured C++ API is only available in this build. The client application may ONLY use MT32EMU_API_TYPE 0.
* 1: The C-compatible API is only available. The library is built as a shared object, only C functions are exported,
* and thus the client application may NOT use MT32EMU_API_TYPE 0.
* 2: The C-compatible API is only available. The library is built as a shared object, only the factory function
* is exported, and thus the client application may ONLY use MT32EMU_API_TYPE 2.
* 3: All the available API types are provided by the library build.
*/
#define MT32EMU_EXPORTS_TYPE 3
#endif

119
src/SOUND/munt/globals.h Normal file
View File

@@ -0,0 +1,119 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_GLOBALS_H
#define MT32EMU_GLOBALS_H
#include "config.h"
/* Support for compiling shared library. */
#ifdef MT32EMU_SHARED
#if defined _WIN32 || defined __CYGWIN__
#ifdef _MSC_VER
#ifdef mt32emu_EXPORTS
#define MT32EMU_EXPORT_ATTRIBUTE _declspec(dllexport)
#else /* #ifdef mt32emu_EXPORTS */
#define MT32EMU_EXPORT_ATTRIBUTE _declspec(dllimport)
#endif /* #ifdef mt32emu_EXPORTS */
#else /* #ifdef _MSC_VER */
#ifdef mt32emu_EXPORTS
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((dllexport))
#else /* #ifdef mt32emu_EXPORTS */
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((dllimport))
#endif /* #ifdef mt32emu_EXPORTS */
#endif /* #ifdef _MSC_VER */
#else /* #if defined _WIN32 || defined __CYGWIN__ */
#define MT32EMU_EXPORT_ATTRIBUTE __attribute__ ((visibility("default")))
#endif /* #if defined _WIN32 || defined __CYGWIN__ */
#else /* #ifdef MT32EMU_SHARED */
#define MT32EMU_EXPORT_ATTRIBUTE
#endif /* #ifdef MT32EMU_SHARED */
#if MT32EMU_EXPORTS_TYPE == 1 || MT32EMU_EXPORTS_TYPE == 2
#define MT32EMU_EXPORT
#else
#define MT32EMU_EXPORT MT32EMU_EXPORT_ATTRIBUTE
#endif
/* Useful constants */
/* Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent.
* In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator,
* except the emulation of analogue path.
* The output from the synth is supposed to be resampled externally in order to convert to the desired sample rate.
*/
#define MT32EMU_SAMPLE_RATE 32000
/* The default value for the maximum number of partials playing simultaneously. */
#define MT32EMU_DEFAULT_MAX_PARTIALS 32
/* The higher this number, the more memory will be used, but the more samples can be processed in one run -
* various parts of sample generation can be processed more efficiently in a single run.
* A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever
* called with will give no gain (but simply waste the memory).
* Note that this value does *not* in any way impose limitations on the length given to render(), and has no effect
* on the generated audio.
* This value must be >= 1.
*/
#define MT32EMU_MAX_SAMPLES_PER_RUN 4096
/* The default size of the internal MIDI event queue.
* It holds the incoming MIDI events before the rendering engine actually processes them.
* The main goal is to fairly emulate the real hardware behaviour which obviously
* uses an internal MIDI event queue to gather incoming data as well as the delays
* introduced by transferring data via the MIDI interface.
* This also facilitates building of an external rendering loop
* as the queue stores timestamped MIDI events.
*/
#define MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE 1024
/* Maximum allowed size of MIDI parser input stream buffer.
* Should suffice for any reasonable bulk dump SysEx, as the h/w units have only 32K of RAM onboard.
*/
#define MT32EMU_MAX_STREAM_BUFFER_SIZE 32768
/* This should correspond to the MIDI buffer size used in real h/w devices.
* CM-32L control ROM seems using 1000 bytes, old MT-32 isn't confirmed by now.
*/
#define MT32EMU_SYSEX_BUFFER_SIZE 1000
#if defined(__cplusplus) && MT32EMU_API_TYPE != 1
namespace MT32Emu
{
const unsigned int SAMPLE_RATE = MT32EMU_SAMPLE_RATE;
#undef MT32EMU_SAMPLE_RATE
const unsigned int DEFAULT_MAX_PARTIALS = MT32EMU_DEFAULT_MAX_PARTIALS;
#undef MT32EMU_DEFAULT_MAX_PARTIALS
const unsigned int MAX_SAMPLES_PER_RUN = MT32EMU_MAX_SAMPLES_PER_RUN;
#undef MT32EMU_MAX_SAMPLES_PER_RUN
const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE;
#undef MT32EMU_DEFAULT_MIDI_EVENT_QUEUE_SIZE
const unsigned int MAX_STREAM_BUFFER_SIZE = MT32EMU_MAX_STREAM_BUFFER_SIZE;
#undef MT32EMU_MAX_STREAM_BUFFER_SIZE
const unsigned int SYSEX_BUFFER_SIZE = MT32EMU_SYSEX_BUFFER_SIZE;
#undef MT32EMU_SYSEX_BUFFER_SIZE
}
#endif /* #if defined(__cplusplus) && MT32EMU_API_TYPE != 1 */
#endif /* #ifndef MT32EMU_GLOBALS_H */

118
src/SOUND/munt/internals.h Normal file
View File

@@ -0,0 +1,118 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_INTERNALS_H
#define MT32EMU_INTERNALS_H
#include "Types.h"
// Debugging
// 0: Standard debug output is not stamped with the rendered sample count
// 1: Standard debug output is stamped with the rendered sample count
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
// This is important to bear in mind for debug output that occurs during a run.
#ifndef MT32EMU_DEBUG_SAMPLESTAMPS
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
#endif
// 0: No debug output for initialisation progress
// 1: Debug output for initialisation progress
#ifndef MT32EMU_MONITOR_INIT
#define MT32EMU_MONITOR_INIT 0
#endif
// 0: No debug output for MIDI events
// 1: Debug output for weird MIDI events
#ifndef MT32EMU_MONITOR_MIDI
#define MT32EMU_MONITOR_MIDI 0
#endif
// 0: No debug output for note on/off
// 1: Basic debug output for note on/off
// 2: Comprehensive debug output for note on/off
#ifndef MT32EMU_MONITOR_INSTRUMENTS
#define MT32EMU_MONITOR_INSTRUMENTS 0
#endif
// 0: No debug output for partial allocations
// 1: Show partial stats when an allocation fails
// 2: Show partial stats with every new poly
// 3: Show individual partial allocations/deactivations
#ifndef MT32EMU_MONITOR_PARTIALS
#define MT32EMU_MONITOR_PARTIALS 0
#endif
// 0: No debug output for sysex
// 1: Basic debug output for sysex
#ifndef MT32EMU_MONITOR_SYSEX
#define MT32EMU_MONITOR_SYSEX 0
#endif
// 0: No debug output for sysex writes to the timbre areas
// 1: Debug output with the name and location of newly-written timbres
// 2: Complete dump of timbre parameters for newly-written timbres
#ifndef MT32EMU_MONITOR_TIMBRES
#define MT32EMU_MONITOR_TIMBRES 0
#endif
// 0: No TVA/TVF-related debug output.
// 1: Shows changes to TVA/TVF target, increment and phase.
#ifndef MT32EMU_MONITOR_TVA
#define MT32EMU_MONITOR_TVA 0
#endif
#ifndef MT32EMU_MONITOR_TVF
#define MT32EMU_MONITOR_TVF 0
#endif
// Configuration
// If non-zero, deletes reverb buffers that are not in use to save memory.
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
#ifndef MT32EMU_REDUCE_REVERB_MEMORY
#define MT32EMU_REDUCE_REVERB_MEMORY 1
#endif
// 0: Maximum speed at the cost of a bit lower emulation accuracy.
// 1: Maximum achievable emulation accuracy.
#ifndef MT32EMU_BOSS_REVERB_PRECISE_MODE
#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0
#endif
namespace MT32Emu {
typedef Bit16s IntSample;
typedef Bit32s IntSampleEx;
typedef float FloatSample;
enum PolyState {
POLY_Playing,
POLY_Held, // This marks keys that have been released on the keyboard, but are being held by the pedal
POLY_Releasing,
POLY_Inactive
};
enum ReverbMode {
REVERB_MODE_ROOM,
REVERB_MODE_HALL,
REVERB_MODE_PLATE,
REVERB_MODE_TAP_DELAY
};
} // namespace MT32Emu
#endif // #ifndef MT32EMU_INTERNALS_H

68
src/SOUND/munt/mmath.h Normal file
View File

@@ -0,0 +1,68 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MMATH_H
#define MT32EMU_MMATH_H
#include <cmath>
namespace MT32Emu {
// Mathematical constants
const double DOUBLE_PI = 3.141592653589793;
const double DOUBLE_LN_10 = 2.302585092994046;
const float FLOAT_PI = 3.1415927f;
const float FLOAT_2PI = 6.2831853f;
const float FLOAT_LN_2 = 0.6931472f;
const float FLOAT_LN_10 = 2.3025851f;
static inline float POWF(float x, float y) {
return pow(x, y);
}
static inline float EXPF(float x) {
return exp(x);
}
static inline float EXP2F(float x) {
#ifdef __APPLE__
// on OSX exp2f() is 1.59 times faster than "exp() and the multiplication with FLOAT_LN_2"
return exp2f(x);
#else
return exp(FLOAT_LN_2 * x);
#endif
}
static inline float EXP10F(float x) {
return exp(FLOAT_LN_10 * x);
}
static inline float LOGF(float x) {
return log(x);
}
static inline float LOG2F(float x) {
return log(x) / FLOAT_LN_2;
}
static inline float LOG10F(float x) {
return log10(x);
}
} // namespace MT32Emu
#endif // #ifndef MT32EMU_MMATH_H

84
src/SOUND/munt/mt32emu.h Normal file
View File

@@ -0,0 +1,84 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2017 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MT32EMU_H
#define MT32EMU_MT32EMU_H
#include "config.h"
/* API Configuration */
/* 0: Use full-featured C++ API. Well suitable when the library is to be linked statically.
* When the library is shared, ABI compatibility may be an issue. Therefore, it should
* only be used within a project comprising of several modules to share the library code.
* 1: Use C-compatible API. Make the library looks as a regular C library with well-defined ABI.
* This is also crucial when the library is to be linked with modules in a different
* language, either statically or dynamically.
* 2: Use plugin-like API via C-interface wrapped in a C++ class. This is mainly intended
* for a shared library being dynamically loaded in run-time. To get access to all the library
* services, a client application only needs to bind with a single factory function.
* 3: Use optimised C++ API compatible with the plugin API (type 2). The facade class also wraps
* the C functions but they are invoked directly. This enables the compiler to generate better
* code for the library when linked statically yet being consistent with the plugin-like API.
*/
#ifdef MT32EMU_API_TYPE
#if MT32EMU_API_TYPE == 0 && (MT32EMU_EXPORTS_TYPE == 1 || MT32EMU_EXPORTS_TYPE == 2)
#error Incompatible setting MT32EMU_API_TYPE=0
#elif MT32EMU_API_TYPE == 1 && (MT32EMU_EXPORTS_TYPE == 0 || MT32EMU_EXPORTS_TYPE == 2)
#error Incompatible setting MT32EMU_API_TYPE=1
#elif MT32EMU_API_TYPE == 2 && (MT32EMU_EXPORTS_TYPE == 0)
#error Incompatible setting MT32EMU_API_TYPE=2
#elif MT32EMU_API_TYPE == 3 && (MT32EMU_EXPORTS_TYPE == 0 || MT32EMU_EXPORTS_TYPE == 2)
#error Incompatible setting MT32EMU_API_TYPE=3
#endif
#else /* #ifdef MT32EMU_API_TYPE */
#if 0 < MT32EMU_EXPORTS_TYPE && MT32EMU_EXPORTS_TYPE < 3
#define MT32EMU_API_TYPE MT32EMU_EXPORTS_TYPE
#else
#define MT32EMU_API_TYPE 0
#endif
#endif /* #ifdef MT32EMU_API_TYPE */
/* MT32EMU_SHARED should be defined when building shared library, especially for Windows platforms. */
/*
#define MT32EMU_SHARED
*/
#include "globals.h"
#if !defined(__cplusplus) || MT32EMU_API_TYPE == 1
#include "c_interface/c_interface.h"
#elif MT32EMU_API_TYPE == 2 || MT32EMU_API_TYPE == 3
#include "c_interface/cpp_interface.h"
#else /* #if !defined(__cplusplus) || MT32EMU_API_TYPE == 1 */
#include "Types.h"
#include "File.h"
#include "FileStream.h"
#include "ROMInfo.h"
#include "Synth.h"
#include "MidiStreamParser.h"
#include "SampleRateConverter.h"
#endif /* #if !defined(__cplusplus) || MT32EMU_API_TYPE == 1 */
#endif /* #ifndef MT32EMU_MT32EMU_H */

View File

@@ -0,0 +1,185 @@
/*
Copyright (c) 2011, Micael Hildenborg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Micael Hildenborg nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Contributors:
Gustav
Several members in the gamedev.se forum.
Gregory Petrosyan
*/
#include "sha1.h"
namespace sha1
{
namespace // local
{
// Rotate an integer value to left.
inline unsigned int rol(const unsigned int value,
const unsigned int steps)
{
return ((value << steps) | (value >> (32 - steps)));
}
// Sets the first 16 integers in the buffert to zero.
// Used for clearing the W buffert.
inline void clearWBuffert(unsigned int* buffert)
{
for (int pos = 16; --pos >= 0;)
{
buffert[pos] = 0;
}
}
void innerHash(unsigned int* result, unsigned int* w)
{
unsigned int a = result[0];
unsigned int b = result[1];
unsigned int c = result[2];
unsigned int d = result[3];
unsigned int e = result[4];
int round = 0;
#define sha1macro(func,val) \
{ \
const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \
e = d; \
d = c; \
c = rol(b, 30); \
b = a; \
a = t; \
}
while (round < 16)
{
sha1macro((b & c) | (~b & d), 0x5a827999)
++round;
}
while (round < 20)
{
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
sha1macro((b & c) | (~b & d), 0x5a827999)
++round;
}
while (round < 40)
{
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
sha1macro(b ^ c ^ d, 0x6ed9eba1)
++round;
}
while (round < 60)
{
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc)
++round;
}
while (round < 80)
{
w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
sha1macro(b ^ c ^ d, 0xca62c1d6)
++round;
}
#undef sha1macro
result[0] += a;
result[1] += b;
result[2] += c;
result[3] += d;
result[4] += e;
}
} // namespace
void calc(const void* src, const int bytelength, unsigned char* hash)
{
// Init the result array.
unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };
// Cast the void src pointer to be the byte array we can work with.
const unsigned char* sarray = static_cast<const unsigned char*>(src);
// The reusable round buffer
unsigned int w[80];
// Loop through all complete 64byte blocks.
const int endOfFullBlocks = bytelength - 64;
int endCurrentBlock;
int currentBlock = 0;
while (currentBlock <= endOfFullBlocks)
{
endCurrentBlock = currentBlock + 64;
// Init the round buffer with the 64 byte block data.
for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4)
{
// This line will swap endian on big endian and keep endian on little endian.
w[roundPos++] = static_cast<unsigned int>(sarray[currentBlock + 3])
| (static_cast<unsigned int>(sarray[currentBlock + 2]) << 8)
| (static_cast<unsigned int>(sarray[currentBlock + 1]) << 16)
| (static_cast<unsigned int>(sarray[currentBlock]) << 24);
}
innerHash(result, w);
}
// Handle the last and not full 64 byte block if existing.
endCurrentBlock = bytelength - currentBlock;
clearWBuffert(w);
int lastBlockBytes = 0;
for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes)
{
w[lastBlockBytes >> 2] |= static_cast<unsigned int>(sarray[lastBlockBytes + currentBlock]) << ((3 - (lastBlockBytes & 3)) << 3);
}
w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3);
if (endCurrentBlock >= 56)
{
innerHash(result, w);
clearWBuffert(w);
}
w[15] = bytelength << 3;
innerHash(result, w);
// Store hash in result pointer, and make sure we get in in the correct order on both endian models.
for (int hashByte = 20; --hashByte >= 0;)
{
hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff;
}
}
void toHexString(const unsigned char* hash, char* hexstring)
{
const char hexDigits[] = { "0123456789abcdef" };
for (int hashByte = 20; --hashByte >= 0;)
{
hexstring[hashByte << 1] = hexDigits[(hash[hashByte] >> 4) & 0xf];
hexstring[(hashByte << 1) + 1] = hexDigits[hash[hashByte] & 0xf];
}
hexstring[40] = 0;
}
} // namespace sha1

View File

@@ -0,0 +1,49 @@
/*
Copyright (c) 2011, Micael Hildenborg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Micael Hildenborg nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SHA1_DEFINED
#define SHA1_DEFINED
namespace sha1
{
/**
@param src points to any kind of data to be hashed.
@param bytelength the number of bytes to hash from the src pointer.
@param hash should point to a buffer of at least 20 bytes of size for storing the sha1 result in.
*/
void calc(const void* src, const int bytelength, unsigned char* hash);
/**
@param hash is 20 bytes of sha1 hash. This is the same data that is the result from the calc function.
@param hexstring should point to a buffer of at least 41 bytes of size for storing the hexadecimal representation of the hash. A zero will be written at position 40, so the buffer will be a valid zero ended string.
*/
void toHexString(const unsigned char* hash, char* hexstring);
} // namespace sha1
#endif // SHA1_DEFINED

View File

@@ -0,0 +1,74 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "InternalResampler.h"
#include <SincResampler.h>
#include <ResamplerModel.h>
#include "../Synth.h"
using namespace SRCTools;
namespace MT32Emu {
class SynthWrapper : public FloatSampleProvider {
Synth &synth;
public:
SynthWrapper(Synth &useSynth) : synth(useSynth)
{}
void getOutputSamples(FloatSample *outBuffer, unsigned int size) {
synth.render(outBuffer, size);
}
};
static FloatSampleProvider &createModel(Synth &synth, SRCTools::FloatSampleProvider &synthSource, double targetSampleRate, SamplerateConversionQuality quality) {
static const double MAX_AUDIBLE_FREQUENCY = 20000.0;
const double sourceSampleRate = synth.getStereoOutputSampleRate();
if (quality != SamplerateConversionQuality_FASTEST) {
const bool oversampledMode = synth.getStereoOutputSampleRate() == Synth::getStereoOutputSampleRate(AnalogOutputMode_OVERSAMPLED);
// Oversampled input allows to bypass IIR interpolation stage and, in some cases, IIR decimation stage
if (oversampledMode && (0.5 * sourceSampleRate) <= targetSampleRate) {
// NOTE: In the oversampled mode, the transition band starts at 20kHz and ends at 28kHz
double passband = MAX_AUDIBLE_FREQUENCY;
double stopband = 0.5 * sourceSampleRate + MAX_AUDIBLE_FREQUENCY;
ResamplerStage &resamplerStage = *SincResampler::createSincResampler(sourceSampleRate, targetSampleRate, passband, stopband, ResamplerModel::DEFAULT_DB_SNR, ResamplerModel::DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR);
return ResamplerModel::createResamplerModel(synthSource, resamplerStage);
}
}
return ResamplerModel::createResamplerModel(synthSource, sourceSampleRate, targetSampleRate, static_cast<ResamplerModel::Quality>(quality));
}
} // namespace MT32Emu
using namespace MT32Emu;
InternalResampler::InternalResampler(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality) :
synthSource(*new SynthWrapper(synth)),
model(createModel(synth, synthSource, targetSampleRate, quality))
{}
InternalResampler::~InternalResampler() {
ResamplerModel::freeResamplerModel(model, synthSource);
delete &synthSource;
}
void InternalResampler::getOutputSamples(float *buffer, unsigned int length) {
model.getOutputSamples(buffer, length);
}

View File

@@ -0,0 +1,42 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_INTERNAL_RESAMPLER_H
#define MT32EMU_INTERNAL_RESAMPLER_H
#include "../Enumerations.h"
#include "FloatSampleProvider.h"
namespace MT32Emu {
class Synth;
class InternalResampler {
public:
InternalResampler(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality);
~InternalResampler();
void getOutputSamples(float *buffer, unsigned int length);
private:
SRCTools::FloatSampleProvider &synthSource;
SRCTools::FloatSampleProvider &model;
};
} // namespace MT32Emu
#endif // MT32EMU_INTERNAL_RESAMPLER_H

View File

@@ -0,0 +1,99 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SamplerateAdapter.h"
#include "../Synth.h"
using namespace MT32Emu;
static const unsigned int CHANNEL_COUNT = 2;
long SamplerateAdapter::getInputSamples(void *cb_data, float **data) {
SamplerateAdapter *instance = static_cast<SamplerateAdapter *>(cb_data);
unsigned int length = instance->inBufferSize < 1 ? 1 : (MAX_SAMPLES_PER_RUN < instance->inBufferSize ? MAX_SAMPLES_PER_RUN : instance->inBufferSize);
instance->synth.render(instance->inBuffer, length);
*data = instance->inBuffer;
instance->inBufferSize -= length;
return length;
}
SamplerateAdapter::SamplerateAdapter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality quality) :
synth(useSynth),
inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN]),
inBufferSize(MAX_SAMPLES_PER_RUN),
inputToOutputRatio(useSynth.getStereoOutputSampleRate() / targetSampleRate),
outputToInputRatio(targetSampleRate / useSynth.getStereoOutputSampleRate())
{
int error;
int conversionType;
switch (quality) {
case SamplerateConversionQuality_FASTEST:
conversionType = SRC_LINEAR;
break;
case SamplerateConversionQuality_FAST:
conversionType = SRC_SINC_FASTEST;
break;
case SamplerateConversionQuality_BEST:
conversionType = SRC_SINC_BEST_QUALITY;
break;
case SamplerateConversionQuality_GOOD:
default:
conversionType = SRC_SINC_MEDIUM_QUALITY;
break;
};
resampler = src_callback_new(getInputSamples, conversionType, CHANNEL_COUNT, &error, this);
if (error != 0) {
synth.printDebug("SamplerateAdapter: Creation of Samplerate instance failed: %s\n", src_strerror(error));
src_delete(resampler);
resampler = NULL;
}
}
SamplerateAdapter::~SamplerateAdapter() {
delete[] inBuffer;
src_delete(resampler);
}
void SamplerateAdapter::getOutputSamples(float *buffer, unsigned int length) {
if (resampler == NULL) {
Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length);
return;
}
while (length > 0) {
inBufferSize = static_cast<unsigned int>(length * inputToOutputRatio + 0.5);
long gotFrames = src_callback_read(resampler, outputToInputRatio, long(length), buffer);
int error = src_error(resampler);
if (error != 0) {
synth.printDebug("SamplerateAdapter: Samplerate error during processing: %s > resetting\n", src_strerror(error));
error = src_reset(resampler);
if (error != 0) {
synth.printDebug("SamplerateAdapter: Samplerate failed to reset: %s\n", src_strerror(error));
src_delete(resampler);
resampler = NULL;
Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length);
synth.printDebug("SamplerateAdapter: Samplerate disabled\n");
return;
}
continue;
}
if (gotFrames <= 0) {
synth.printDebug("SamplerateAdapter: got %li frames from Samplerate, weird\n", gotFrames);
}
buffer += CHANNEL_COUNT * gotFrames;
length -= gotFrames;
}
}

View File

@@ -0,0 +1,48 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_SAMPLERATE_ADAPTER_H
#define MT32EMU_SAMPLERATE_ADAPTER_H
#include <samplerate.h>
#include "../Enumerations.h"
namespace MT32Emu {
class Synth;
class SamplerateAdapter {
public:
SamplerateAdapter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality);
~SamplerateAdapter();
void getOutputSamples(float *outBuffer, unsigned int length);
private:
Synth &synth;
float * const inBuffer;
unsigned int inBufferSize;
const double inputToOutputRatio;
const double outputToInputRatio;
SRC_STATE *resampler;
static long getInputSamples(void *cb_data, float **data);
};
} // namespace MT32Emu
#endif // MT32EMU_SAMPLERATE_ADAPTER_H

View File

@@ -0,0 +1,106 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SoxrAdapter.h"
#include "../Synth.h"
using namespace MT32Emu;
static const unsigned int CHANNEL_COUNT = 2;
size_t SoxrAdapter::getInputSamples(void *input_fn_state, soxr_in_t *data, size_t requested_len) {
unsigned int length = requested_len < 1 ? 1 : (MAX_SAMPLES_PER_RUN < requested_len ? MAX_SAMPLES_PER_RUN : static_cast<unsigned int>(requested_len));
SoxrAdapter *instance = static_cast<SoxrAdapter *>(input_fn_state);
instance->synth.render(instance->inBuffer, length);
*data = instance->inBuffer;
return length;
}
SoxrAdapter::SoxrAdapter(Synth &useSynth, double targetSampleRate, SamplerateConversionQuality quality) :
synth(useSynth),
inBuffer(new float[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN])
{
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I);
unsigned long qualityRecipe;
switch (quality) {
case SamplerateConversionQuality_FASTEST:
qualityRecipe = SOXR_QQ;
break;
case SamplerateConversionQuality_FAST:
qualityRecipe = SOXR_LQ;
break;
case SamplerateConversionQuality_GOOD:
qualityRecipe = SOXR_MQ;
break;
case SamplerateConversionQuality_BEST:
default:
qualityRecipe = SOXR_16_BITQ;
break;
};
soxr_quality_spec_t qSpec = soxr_quality_spec(qualityRecipe, 0);
soxr_runtime_spec_t rtSpec = soxr_runtime_spec(1);
soxr_error_t error;
resampler = soxr_create(synth.getStereoOutputSampleRate(), targetSampleRate, CHANNEL_COUNT, &error, &ioSpec, &qSpec, &rtSpec);
if (error != NULL) {
synth.printDebug("SoxrAdapter: Creation of SOXR instance failed: %s\n", soxr_strerror(error));
soxr_delete(resampler);
resampler = NULL;
return;
}
error = soxr_set_input_fn(resampler, getInputSamples, this, MAX_SAMPLES_PER_RUN);
if (error != NULL) {
synth.printDebug("SoxrAdapter: Installing sample feed for SOXR failed: %s\n", soxr_strerror(error));
soxr_delete(resampler);
resampler = NULL;
}
}
SoxrAdapter::~SoxrAdapter() {
delete[] inBuffer;
if (resampler != NULL) {
soxr_delete(resampler);
}
}
void SoxrAdapter::getOutputSamples(float *buffer, unsigned int length) {
if (resampler == NULL) {
Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length);
return;
}
while (length > 0) {
size_t gotFrames = soxr_output(resampler, buffer, size_t(length));
soxr_error_t error = soxr_error(resampler);
if (error != NULL) {
synth.printDebug("SoxrAdapter: SOXR error during processing: %s > resetting\n", soxr_strerror(error));
error = soxr_clear(resampler);
if (error != NULL) {
synth.printDebug("SoxrAdapter: SOXR failed to reset: %s\n", soxr_strerror(error));
soxr_delete(resampler);
resampler = NULL;
Synth::muteSampleBuffer(buffer, CHANNEL_COUNT * length);
synth.printDebug("SoxrAdapter: SOXR disabled\n");
return;
}
continue;
}
if (gotFrames == 0) {
synth.printDebug("SoxrAdapter: got 0 frames from SOXR, weird\n");
}
buffer += CHANNEL_COUNT * gotFrames;
length -= static_cast<unsigned int>(gotFrames);
}
}

View File

@@ -0,0 +1,45 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_SOXR_ADAPTER_H
#define MT32EMU_SOXR_ADAPTER_H
#include <soxr.h>
#include "../Enumerations.h"
namespace MT32Emu {
class Synth;
class SoxrAdapter {
public:
SoxrAdapter(Synth &synth, double targetSampleRate, SamplerateConversionQuality quality);
~SoxrAdapter();
void getOutputSamples(float *buffer, unsigned int length);
private:
Synth &synth;
float * const inBuffer;
soxr_t resampler;
static size_t getInputSamples(void *input_fn_state, soxr_in_t *data, size_t requested_len);
};
} // namespace MT32Emu
#endif // MT32EMU_SOXR_ADAPTER_H

View File

@@ -0,0 +1,67 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_FIR_RESAMPLER_H
#define SRCTOOLS_FIR_RESAMPLER_H
#include "ResamplerStage.h"
namespace SRCTools {
typedef FloatSample FIRCoefficient;
static const unsigned int FIR_INTERPOLATOR_CHANNEL_COUNT = 2;
class FIRResampler : public ResamplerStage {
public:
FIRResampler(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength);
~FIRResampler();
void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength);
unsigned int estimateInLength(const unsigned int outLength) const;
private:
const struct Constants {
// Filter coefficients
const FIRCoefficient *taps;
// Indicates whether to interpolate filter taps
bool usePhaseInterpolation;
// Size of array of filter coefficients
unsigned int numberOfTaps;
// Upsampling factor
unsigned int numberOfPhases;
// Downsampling factor
double phaseIncrement;
// Index of last delay line element, generally greater than numberOfTaps to form a proper binary mask
unsigned int delayLineMask;
// Delay line
FloatSample(*ringBuffer)[FIR_INTERPOLATOR_CHANNEL_COUNT];
Constants(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength);
} constants;
// Index of current sample in delay line
unsigned int ringBufferPosition;
// Current phase
double phase;
bool needNextInSample() const;
void addInSamples(const FloatSample *&inSamples);
void getOutSamplesStereo(FloatSample *&outSamples);
}; // class FIRResampler
} // namespace SRCTools
#endif // SRCTOOLS_FIR_RESAMPLER_H

View File

@@ -0,0 +1,34 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H
#define SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H
namespace SRCTools {
typedef float FloatSample;
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
class FloatSampleProvider {
public:
virtual ~FloatSampleProvider() {};
virtual void getOutputSamples(FloatSample *outBuffer, unsigned int size) = 0;
};
} // namespace SRCTools
#endif // SRCTOOLS_FLOAT_SAMPLE_PROVIDER_H

View File

@@ -0,0 +1,100 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_IIR_2X_RESAMPLER_H
#define SRCTOOLS_IIR_2X_RESAMPLER_H
#include "ResamplerStage.h"
namespace SRCTools {
static const unsigned int IIR_RESAMPER_CHANNEL_COUNT = 2;
static const unsigned int IIR_SECTION_ORDER = 2;
typedef FloatSample IIRCoefficient;
typedef FloatSample BufferedSample;
typedef BufferedSample SectionBuffer[IIR_SECTION_ORDER];
// Non-trivial coefficients of a 2nd-order section of a parallel bank
// (zero-order numerator coefficient is always zero, zero-order denominator coefficient is always unity)
struct IIRSection {
IIRCoefficient num1;
IIRCoefficient num2;
IIRCoefficient den1;
IIRCoefficient den2;
};
class IIRResampler : public ResamplerStage {
public:
enum Quality {
// Used when providing custom IIR filter coefficients.
CUSTOM,
// Use fast elliptic filter with symmetric ripple: N=8, Ap=As=-99 dB, fp=0.125, fs = 0.25 (in terms of sample rate)
FAST,
// Use average elliptic filter with symmetric ripple: N=12, Ap=As=-106 dB, fp=0.193, fs = 0.25 (in terms of sample rate)
GOOD,
// Use sharp elliptic filter with symmetric ripple: N=18, Ap=As=-106 dB, fp=0.238, fs = 0.25 (in terms of sample rate)
BEST
};
// Returns the retained fraction of the passband for the given standard quality value
static double getPassbandFractionForQuality(Quality quality);
protected:
explicit IIRResampler(const Quality quality);
explicit IIRResampler(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]);
~IIRResampler();
const struct Constants {
// Coefficient of the 0-order FIR part
IIRCoefficient fir;
// 2nd-order sections that comprise a parallel bank
const IIRSection *sections;
// Number of 2nd-order sections
unsigned int sectionsCount;
// Delay line per channel per section
SectionBuffer *buffer;
Constants(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[], const Quality quality);
} constants;
}; // class IIRResampler
class IIR2xInterpolator : public IIRResampler {
public:
explicit IIR2xInterpolator(const Quality quality);
explicit IIR2xInterpolator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]);
void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength);
unsigned int estimateInLength(const unsigned int outLength) const;
private:
FloatSample lastInputSamples[IIR_RESAMPER_CHANNEL_COUNT];
unsigned int phase;
};
class IIR2xDecimator : public IIRResampler {
public:
explicit IIR2xDecimator(const Quality quality);
explicit IIR2xDecimator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]);
void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength);
unsigned int estimateInLength(const unsigned int outLength) const;
};
} // namespace SRCTools
#endif // SRCTOOLS_IIR_2X_RESAMPLER_H

View File

@@ -0,0 +1,42 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_LINEAR_RESAMPLER_H
#define SRCTOOLS_LINEAR_RESAMPLER_H
#include "ResamplerStage.h"
namespace SRCTools {
static const unsigned int LINEAR_RESAMPER_CHANNEL_COUNT = 2;
class LinearResampler : public ResamplerStage {
public:
LinearResampler(double sourceSampleRate, double targetSampleRate);
~LinearResampler() {}
unsigned int estimateInLength(const unsigned int outLength) const;
void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength);
private:
const double inputToOutputRatio;
double position;
FloatSample lastInputSamples[LINEAR_RESAMPER_CHANNEL_COUNT];
};
} // namespace SRCTools
#endif // SRCTOOLS_LINEAR_RESAMPLER_H

View File

@@ -0,0 +1,63 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_RESAMPLER_MODEL_H
#define SRCTOOLS_RESAMPLER_MODEL_H
#include "FloatSampleProvider.h"
namespace SRCTools {
class ResamplerStage;
/** Model consists of one or more ResampleStage instances connected in a cascade. */
namespace ResamplerModel {
// Seems to be a good choice for 16-bit integer samples.
static const double DEFAULT_DB_SNR = 106;
// When using linear interpolation, oversampling factor necessary to achieve the DEFAULT_DB_SNR is about 256.
// This figure is the upper estimation, and it can be found by analysing the frequency response of the linear interpolator.
// When less SNR is desired, this value should also decrease in accordance.
static const unsigned int DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR = 256;
// In the default resampler model, the input to the windowed sinc filter is always at least 2x oversampled during upsampling,
// so oversampling factor of 128 should be sufficient to achieve the DEFAULT_DB_SNR with linear interpolation.
static const unsigned int DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR = DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR / 2;
enum Quality {
// Use when the speed is more important than the audio quality.
FASTEST,
// Use FAST quality setting of the IIR stage (50% of passband retained).
FAST,
// Use GOOD quality setting of the IIR stage (77% of passband retained).
GOOD,
// Use BEST quality setting of the IIR stage (95% of passband retained).
BEST
};
FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, double sourceSampleRate, double targetSampleRate, Quality quality);
FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, ResamplerStage **stages, unsigned int stageCount);
FloatSampleProvider &createResamplerModel(FloatSampleProvider &source, ResamplerStage &stage);
void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source);
} // namespace ResamplerModel
} // namespace SRCTools
#endif // SRCTOOLS_RESAMPLER_MODEL_H

View File

@@ -0,0 +1,38 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_RESAMPLER_STAGE_H
#define SRCTOOLS_RESAMPLER_STAGE_H
#include "FloatSampleProvider.h"
namespace SRCTools {
/** Interface defines an abstract source of samples. It can either define a single channel stream or a stream with interleaved channels. */
class ResamplerStage {
public:
virtual ~ResamplerStage() {};
/** Returns a lower estimation of required number of input samples to produce the specified number of output samples. */
virtual unsigned int estimateInLength(const unsigned int outLength) const = 0;
/** Generates output samples. The arguments are adjusted in accordance with the number of samples processed. */
virtual void process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) = 0;
};
} // namespace SRCTools
#endif // SRCTOOLS_RESAMPLER_STAGE_H

View File

@@ -0,0 +1,46 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SRCTOOLS_SINC_RESAMPLER_H
#define SRCTOOLS_SINC_RESAMPLER_H
#include "FIRResampler.h"
namespace SRCTools {
class ResamplerStage;
namespace SincResampler {
ResamplerStage *createSincResampler(const double inputFrequency, const double outputFrequency, const double passbandFrequency, const double stopbandFrequency, const double dbSNR, const unsigned int maxUpsampleFactor);
namespace Utils {
void computeResampleFactors(unsigned int &upsampleFactor, double &downsampleFactor, const double inputFrequency, const double outputFrequency, const unsigned int maxUpsampleFactor);
unsigned int greatestCommonDivisor(unsigned int a, unsigned int b);
}
namespace KaizerWindow {
double estimateBeta(double dbRipple);
unsigned int estimateOrder(double dbRipple, double fp, double fs);
double bessel(const double x);
void windowedSinc(FIRCoefficient kernel[], const unsigned int order, const double fc, const double beta, const double amp);
}
} // namespace SincResampler
} // namespace SRCTools
#endif // SRCTOOLS_SINC_RESAMPLER_H

View File

@@ -0,0 +1,107 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <cstring>
#include "FIRResampler.h"
using namespace SRCTools;
FIRResampler::Constants::Constants(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength) {
usePhaseInterpolation = downsampleFactor != floor(downsampleFactor);
FIRCoefficient *kernelCopy = new FIRCoefficient[kernelLength];
memcpy(kernelCopy, kernel, kernelLength * sizeof(FIRCoefficient));
taps = kernelCopy;
numberOfTaps = kernelLength;
numberOfPhases = upsampleFactor;
phaseIncrement = downsampleFactor;
unsigned int minDelayLineLength = static_cast<unsigned int>(ceil(double(kernelLength) / upsampleFactor));
unsigned int delayLineLength = 2;
while (delayLineLength < minDelayLineLength) delayLineLength <<= 1;
delayLineMask = delayLineLength - 1;
ringBuffer = new FloatSample[delayLineLength][FIR_INTERPOLATOR_CHANNEL_COUNT];
FloatSample *s = *ringBuffer;
FloatSample *e = ringBuffer[delayLineLength];
while (s < e) *(s++) = 0;
}
FIRResampler::FIRResampler(const unsigned int upsampleFactor, const double downsampleFactor, const FIRCoefficient kernel[], const unsigned int kernelLength) :
constants(upsampleFactor, downsampleFactor, kernel, kernelLength),
ringBufferPosition(0),
phase(constants.numberOfPhases)
{}
FIRResampler::~FIRResampler() {
delete[] constants.ringBuffer;
delete[] constants.taps;
}
void FIRResampler::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) {
while (outLength > 0) {
while (needNextInSample()) {
if (inLength == 0) return;
addInSamples(inSamples);
--inLength;
}
getOutSamplesStereo(outSamples);
--outLength;
}
}
unsigned int FIRResampler::estimateInLength(const unsigned int outLength) const {
return static_cast<unsigned int>((outLength * constants.phaseIncrement + phase) / constants.numberOfPhases);
}
bool FIRResampler::needNextInSample() const {
return constants.numberOfPhases <= phase;
}
void FIRResampler::addInSamples(const FloatSample *&inSamples) {
ringBufferPosition = (ringBufferPosition - 1) & constants.delayLineMask;
for (unsigned int i = 0; i < FIR_INTERPOLATOR_CHANNEL_COUNT; i++) {
constants.ringBuffer[ringBufferPosition][i] = *(inSamples++);
}
phase -= constants.numberOfPhases;
}
// Optimised for processing stereo interleaved streams
void FIRResampler::getOutSamplesStereo(FloatSample *&outSamples) {
FloatSample leftSample = 0.0;
FloatSample rightSample = 0.0;
unsigned int delaySampleIx = ringBufferPosition;
if (constants.usePhaseInterpolation) {
double phaseFraction = phase - floor(phase);
unsigned int maxTapIx = phaseFraction == 0 ? constants.numberOfTaps : constants.numberOfTaps - 1;
for (unsigned int tapIx = static_cast<unsigned int>(phase); tapIx < maxTapIx; tapIx += constants.numberOfPhases) {
FIRCoefficient tap = FIRCoefficient(constants.taps[tapIx] + (constants.taps[tapIx + 1] - constants.taps[tapIx]) * phaseFraction);
leftSample += tap * constants.ringBuffer[delaySampleIx][0];
rightSample += tap * constants.ringBuffer[delaySampleIx][1];
delaySampleIx = (delaySampleIx + 1) & constants.delayLineMask;
}
} else {
// Optimised for rational resampling ratios when phase is always integer
for (unsigned int tapIx = static_cast<unsigned int>(phase); tapIx < constants.numberOfTaps; tapIx += constants.numberOfPhases) {
FIRCoefficient tap = constants.taps[tapIx];
leftSample += tap * constants.ringBuffer[delaySampleIx][0];
rightSample += tap * constants.ringBuffer[delaySampleIx][1];
delaySampleIx = (delaySampleIx + 1) & constants.delayLineMask;
}
}
*(outSamples++) = leftSample;
*(outSamples++) = rightSample;
phase += constants.phaseIncrement;
}

View File

@@ -0,0 +1,229 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include "IIR2xResampler.h"
namespace SRCTools {
// Avoid denormals degrading performance, using biased input
static const BufferedSample BIAS = 1e-20f;
// Sharp elliptic filter with symmetric ripple: N=18, Ap=As=-106 dB, fp=0.238, fs = 0.25 (in terms of sample rate)
static const IIRCoefficient FIR_BEST = 0.0014313792470984f;
static const IIRSection SECTIONS_BEST[] = {
{ 2.85800356692148000f,-0.2607342682253230f,-0.602478421807085f, 0.109823442522145f },
{ -4.39519408383016000f, 1.4651975326003500f,-0.533817668127954f, 0.226045921792036f },
{ 0.86638550740991800f,-2.1053851417898500f,-0.429134968401065f, 0.403512574222174f },
{ 1.67161485530774000f, 0.7963595880494520f,-0.324989203363446f, 0.580756666711889f },
{ -1.19962759276471000f, 0.5873595178851540f,-0.241486447489019f, 0.724264899930934f },
{ 0.01631779946479250f,-0.6282334739461620f,-0.182766025706656f, 0.827774001858882f },
{ 0.28404415859352400f, 0.1038619997715160f,-0.145276649558926f, 0.898510501923554f },
{ -0.08105788424234910f, 0.0781551578108934f,-0.123965846623366f, 0.947105257601873f },
{ -0.00872608905948005f,-0.0222098231712466f,-0.115056854360748f, 0.983542001125711f }
};
// Average elliptic filter with symmetric ripple: N=12, Ap=As=-106 dB, fp=0.193, fs = 0.25 (in terms of sample rate)
static const IIRCoefficient FIR_GOOD = 0.000891054570268146f;
static const IIRSection SECTIONS_GOOD[] = {
{ 2.2650157226725700f,-0.4034180565140230f,-0.750061486095301f, 0.157801404511953f },
{ -3.2788261989161700f, 1.3952152147542600f,-0.705854270206788f, 0.265564985645774f },
{ 0.4397975114813240f,-1.3957634748753100f,-0.639718853965265f, 0.435324134360315f },
{ 0.9827040216680520f, 0.1837182774040940f,-0.578569965618418f, 0.615205557837542f },
{ -0.3759752818621670f, 0.3266073609399490f,-0.540913588637109f, 0.778264420176574f },
{ -0.0253548089519618f,-0.0925779221603846f,-0.537704370375240f, 0.925800083252964f }
};
// Fast elliptic filter with symmetric ripple: N=8, Ap=As=-99 dB, fp=0.125, fs = 0.25 (in terms of sample rate)
static const IIRCoefficient FIR_FAST = 0.000882837778745889f;
static const IIRSection SECTIONS_FAST[] = {
{ 1.215377077431620f,-0.35864455030878000f,-0.972220718789242f, 0.252934735930620f },
{ -1.525654419254140f, 0.86784918631245500f,-0.977713689358124f, 0.376580616703668f },
{ 0.136094441564220f,-0.50414116798010400f,-1.007004471865290f, 0.584048854845331f },
{ 0.180604082285806f,-0.00467624342403851f,-1.093486919012100f, 0.844904524843996f }
};
static inline BufferedSample calcNumerator(const IIRSection &section, const BufferedSample buffer1, const BufferedSample buffer2) {
return section.num1 * buffer1 + section.num2 * buffer2;
}
static inline BufferedSample calcDenominator(const IIRSection &section, const BufferedSample input, const BufferedSample buffer1, const BufferedSample buffer2) {
return input - section.den1 * buffer1 - section.den2 * buffer2;
}
} // namespace SRCTools
using namespace SRCTools;
double IIRResampler::getPassbandFractionForQuality(Quality quality) {
switch (quality) {
case FAST:
return 0.5;
case GOOD:
return 0.7708;
case BEST:
return 0.9524;
default:
return 0;
}
}
IIRResampler::Constants::Constants(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[], const Quality quality) {
if (quality == CUSTOM) {
sectionsCount = useSectionsCount;
fir = useFIR;
sections = useSections;
} else {
unsigned int sectionsSize;
switch (quality) {
case FAST:
fir = FIR_FAST;
sections = SECTIONS_FAST;
sectionsSize = sizeof(SECTIONS_FAST);
break;
case GOOD:
fir = FIR_GOOD;
sections = SECTIONS_GOOD;
sectionsSize = sizeof(SECTIONS_GOOD);
break;
case BEST:
fir = FIR_BEST;
sections = SECTIONS_BEST;
sectionsSize = sizeof(SECTIONS_BEST);
break;
default:
sectionsSize = 0;
break;
}
sectionsCount = (sectionsSize / sizeof(IIRSection));
}
const unsigned int delayLineSize = IIR_RESAMPER_CHANNEL_COUNT * sectionsCount;
buffer = new SectionBuffer[delayLineSize];
BufferedSample *s = buffer[0];
BufferedSample *e = buffer[delayLineSize];
while (s < e) *(s++) = 0;
}
IIRResampler::IIRResampler(const Quality quality) :
constants(0, 0.0f, NULL, quality)
{}
IIRResampler::IIRResampler(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) :
constants(useSectionsCount, useFIR, useSections, IIRResampler::CUSTOM)
{}
IIRResampler::~IIRResampler() {
delete[] constants.buffer;
}
IIR2xInterpolator::IIR2xInterpolator(const Quality quality) :
IIRResampler(quality),
phase(1)
{
for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) {
lastInputSamples[chIx] = 0;
}
}
IIR2xInterpolator::IIR2xInterpolator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) :
IIRResampler(useSectionsCount, useFIR, useSections),
phase(1)
{
for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) {
lastInputSamples[chIx] = 0;
}
}
void IIR2xInterpolator::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) {
static const IIRCoefficient INTERPOLATOR_AMP = 2.0;
while (outLength > 0 && inLength > 0) {
SectionBuffer *bufferp = constants.buffer;
for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) {
const FloatSample lastInputSample = lastInputSamples[chIx];
const FloatSample inSample = inSamples[chIx];
BufferedSample tmpOut = phase == 0 ? 0 : inSample * constants.fir;
for (unsigned int i = 0; i < constants.sectionsCount; ++i) {
const IIRSection &section = constants.sections[i];
SectionBuffer &buffer = *bufferp;
// For 2x interpolation, calculation of the numerator reduces to a single multiplication depending on the phase.
if (phase == 0) {
const BufferedSample numOutSample = section.num1 * lastInputSample;
const BufferedSample denOutSample = calcDenominator(section, BIAS + numOutSample, buffer[0], buffer[1]);
buffer[1] = denOutSample;
tmpOut += denOutSample;
} else {
const BufferedSample numOutSample = section.num2 * lastInputSample;
const BufferedSample denOutSample = calcDenominator(section, BIAS + numOutSample, buffer[1], buffer[0]);
buffer[0] = denOutSample;
tmpOut += denOutSample;
}
bufferp++;
}
*(outSamples++) = FloatSample(INTERPOLATOR_AMP * tmpOut);
if (phase > 0) {
lastInputSamples[chIx] = inSample;
}
}
outLength--;
if (phase > 0) {
inSamples += IIR_RESAMPER_CHANNEL_COUNT;
inLength--;
phase = 0;
} else {
phase = 1;
}
}
}
unsigned int IIR2xInterpolator::estimateInLength(const unsigned int outLength) const {
return outLength >> 1;
}
IIR2xDecimator::IIR2xDecimator(const Quality quality) :
IIRResampler(quality)
{}
IIR2xDecimator::IIR2xDecimator(const unsigned int useSectionsCount, const IIRCoefficient useFIR, const IIRSection useSections[]) :
IIRResampler(useSectionsCount, useFIR, useSections)
{}
void IIR2xDecimator::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) {
while (outLength > 0 && inLength > 1) {
SectionBuffer *bufferp = constants.buffer;
for (unsigned int chIx = 0; chIx < IIR_RESAMPER_CHANNEL_COUNT; ++chIx) {
BufferedSample tmpOut = inSamples[chIx] * constants.fir;
for (unsigned int i = 0; i < constants.sectionsCount; ++i) {
const IIRSection &section = constants.sections[i];
SectionBuffer &buffer = *bufferp;
// For 2x decimation, calculation of the numerator is not performed for odd output samples which are to be omitted.
tmpOut += calcNumerator(section, buffer[0], buffer[1]);
buffer[1] = calcDenominator(section, BIAS + inSamples[chIx], buffer[0], buffer[1]);
buffer[0] = calcDenominator(section, BIAS + inSamples[chIx + IIR_RESAMPER_CHANNEL_COUNT], buffer[1], buffer[0]);
bufferp++;
}
*(outSamples++) = FloatSample(tmpOut);
}
outLength--;
inLength -= 2;
inSamples += 2 * IIR_RESAMPER_CHANNEL_COUNT;
}
}
unsigned int IIR2xDecimator::estimateInLength(const unsigned int outLength) const {
return outLength << 1;
}

View File

@@ -0,0 +1,47 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LinearResampler.h"
using namespace SRCTools;
LinearResampler::LinearResampler(double sourceSampleRate, double targetSampleRate) :
inputToOutputRatio(sourceSampleRate / targetSampleRate),
position(1.0) // Preload delay line which effectively makes resampler zero phase
{}
void LinearResampler::process(const FloatSample *&inSamples, unsigned int &inLength, FloatSample *&outSamples, unsigned int &outLength) {
if (inLength == 0) return;
while (outLength > 0) {
while (1.0 <= position) {
position--;
inLength--;
for (unsigned int chIx = 0; chIx < LINEAR_RESAMPER_CHANNEL_COUNT; ++chIx) {
lastInputSamples[chIx] = *(inSamples++);
}
if (inLength == 0) return;
}
for (unsigned int chIx = 0; chIx < LINEAR_RESAMPER_CHANNEL_COUNT; chIx++) {
*(outSamples++) = FloatSample(lastInputSamples[chIx] + position * (inSamples[chIx] - lastInputSamples[chIx]));
}
outLength--;
position += inputToOutputRatio;
}
}
unsigned int LinearResampler::estimateInLength(const unsigned int outLength) const {
return static_cast<unsigned int>(outLength * inputToOutputRatio);
}

View File

@@ -0,0 +1,153 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <cstddef>
#include "ResamplerModel.h"
#include "ResamplerStage.h"
#include "SincResampler.h"
#include "IIR2xResampler.h"
#include "LinearResampler.h"
namespace SRCTools {
namespace ResamplerModel {
static const unsigned int CHANNEL_COUNT = 2;
static const unsigned int MAX_SAMPLES_PER_RUN = 4096;
class CascadeStage : public FloatSampleProvider {
friend void freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source);
public:
CascadeStage(FloatSampleProvider &source, ResamplerStage &resamplerStage);
void getOutputSamples(FloatSample *outBuffer, unsigned int size);
protected:
ResamplerStage &resamplerStage;
private:
FloatSampleProvider &source;
FloatSample buffer[CHANNEL_COUNT * MAX_SAMPLES_PER_RUN];
const FloatSample *bufferPtr;
unsigned int size;
};
class InternalResamplerCascadeStage : public CascadeStage {
public:
InternalResamplerCascadeStage(FloatSampleProvider &useSource, ResamplerStage &useResamplerStage) :
CascadeStage(useSource, useResamplerStage)
{}
~InternalResamplerCascadeStage() {
delete &resamplerStage;
}
};
} // namespace ResamplerModel
} // namespace SRCTools
using namespace SRCTools;
FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, double sourceSampleRate, double targetSampleRate, Quality quality) {
if (sourceSampleRate == targetSampleRate) {
return source;
}
if (quality == FASTEST) {
return *new InternalResamplerCascadeStage(source, *new LinearResampler(sourceSampleRate, targetSampleRate));
}
const IIRResampler::Quality iirQuality = static_cast<IIRResampler::Quality>(quality);
const double iirPassbandFraction = IIRResampler::getPassbandFractionForQuality(iirQuality);
if (sourceSampleRate < targetSampleRate) {
ResamplerStage *iir2xInterpolator = new IIR2xInterpolator(iirQuality);
FloatSampleProvider &iir2xInterpolatorStage = *new InternalResamplerCascadeStage(source, *iir2xInterpolator);
if (2.0 * sourceSampleRate == targetSampleRate) {
return iir2xInterpolatorStage;
}
double passband = 0.5 * sourceSampleRate * iirPassbandFraction;
double stopband = 1.5 * sourceSampleRate;
ResamplerStage *sincResampler = SincResampler::createSincResampler(2.0 * sourceSampleRate, targetSampleRate, passband, stopband, DEFAULT_DB_SNR, DEFAULT_WINDOWED_SINC_MAX_UPSAMPLE_FACTOR);
return *new InternalResamplerCascadeStage(iir2xInterpolatorStage, *sincResampler);
}
if (sourceSampleRate == 2.0 * targetSampleRate) {
ResamplerStage *iir2xDecimator = new IIR2xDecimator(iirQuality);
return *new InternalResamplerCascadeStage(source, *iir2xDecimator);
}
double passband = 0.5 * targetSampleRate * iirPassbandFraction;
double stopband = 1.5 * targetSampleRate;
double sincOutSampleRate = 2.0 * targetSampleRate;
const unsigned int maxUpsampleFactor = static_cast<unsigned int>(ceil(DEFAULT_WINDOWED_SINC_MAX_DOWNSAMPLE_FACTOR * sincOutSampleRate / sourceSampleRate));
ResamplerStage *sincResampler = SincResampler::createSincResampler(sourceSampleRate, sincOutSampleRate, passband, stopband, DEFAULT_DB_SNR, maxUpsampleFactor);
FloatSampleProvider &sincResamplerStage = *new InternalResamplerCascadeStage(source, *sincResampler);
ResamplerStage *iir2xDecimator = new IIR2xDecimator(iirQuality);
return *new InternalResamplerCascadeStage(sincResamplerStage, *iir2xDecimator);
}
FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, ResamplerStage **resamplerStages, unsigned int stageCount) {
FloatSampleProvider *prevStage = &source;
for (unsigned int i = 0; i < stageCount; i++) {
prevStage = new CascadeStage(*prevStage, *(resamplerStages[i]));
}
return *prevStage;
}
FloatSampleProvider &ResamplerModel::createResamplerModel(FloatSampleProvider &source, ResamplerStage &stage) {
return *new CascadeStage(source, stage);
}
void ResamplerModel::freeResamplerModel(FloatSampleProvider &model, FloatSampleProvider &source) {
FloatSampleProvider *currentStage = &model;
while (currentStage != &source) {
CascadeStage *cascadeStage = dynamic_cast<CascadeStage *>(currentStage);
if (cascadeStage == NULL) return;
FloatSampleProvider &prevStage = cascadeStage->source;
delete currentStage;
currentStage = &prevStage;
}
}
using namespace ResamplerModel;
CascadeStage::CascadeStage(FloatSampleProvider &useSource, ResamplerStage &useResamplerStage) :
resamplerStage(useResamplerStage),
source(useSource),
bufferPtr(buffer),
size()
{}
void CascadeStage::getOutputSamples(FloatSample *outBuffer, unsigned int length) {
while (length > 0) {
if (size == 0) {
size = resamplerStage.estimateInLength(length);
if (size < 1) {
size = 1;
} else if (MAX_SAMPLES_PER_RUN < size) {
size = MAX_SAMPLES_PER_RUN;
}
source.getOutputSamples(buffer, size);
bufferPtr = buffer;
}
resamplerStage.process(bufferPtr, size, outBuffer, length);
}
}

View File

@@ -0,0 +1,136 @@
/* Copyright (C) 2015-2017 Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#ifdef SRCTOOLS_SINC_RESAMPLER_DEBUG_LOG
#include <iostream>
#endif
#include "SincResampler.h"
#ifndef M_PI
static const double M_PI = 3.1415926535897932;
#endif
using namespace SRCTools;
using namespace SincResampler;
using namespace Utils;
void Utils::computeResampleFactors(unsigned int &upsampleFactor, double &downsampleFactor, const double inputFrequency, const double outputFrequency, const unsigned int maxUpsampleFactor) {
static const double RATIONAL_RATIO_ACCURACY_FACTOR = 1E15;
upsampleFactor = static_cast<unsigned int>(outputFrequency);
unsigned int downsampleFactorInt = static_cast<unsigned int>(inputFrequency);
if ((upsampleFactor == outputFrequency) && (downsampleFactorInt == inputFrequency)) {
// Input and output frequencies are integers, try to reduce them
const unsigned int gcd = greatestCommonDivisor(upsampleFactor, downsampleFactorInt);
if (gcd > 1) {
upsampleFactor /= gcd;
downsampleFactor = downsampleFactorInt / gcd;
} else {
downsampleFactor = downsampleFactorInt;
}
if (upsampleFactor <= maxUpsampleFactor) return;
} else {
// Try to recover rational resample ratio by brute force
double inputToOutputRatio = inputFrequency / outputFrequency;
for (unsigned int i = 1; i <= maxUpsampleFactor; ++i) {
double testFactor = inputToOutputRatio * i;
if (floor(RATIONAL_RATIO_ACCURACY_FACTOR * testFactor + 0.5) == RATIONAL_RATIO_ACCURACY_FACTOR * floor(testFactor + 0.5)) {
// inputToOutputRatio found to be rational within the accuracy
upsampleFactor = i;
downsampleFactor = floor(testFactor + 0.5);
return;
}
}
}
// Use interpolation of FIR taps as the last resort
upsampleFactor = maxUpsampleFactor;
downsampleFactor = maxUpsampleFactor * inputFrequency / outputFrequency;
}
unsigned int Utils::greatestCommonDivisor(unsigned int a, unsigned int b) {
while (0 < b) {
unsigned int r = a % b;
a = b;
b = r;
}
return a;
}
double KaizerWindow::estimateBeta(double dbRipple) {
return 0.1102 * (dbRipple - 8.7);
}
unsigned int KaizerWindow::estimateOrder(double dbRipple, double fp, double fs) {
const double transBW = (fs - fp);
return static_cast<unsigned int>(ceil((dbRipple - 8) / (2.285 * 2 * M_PI * transBW)));
}
double KaizerWindow::bessel(const double x) {
static const double EPS = 1.11E-16;
double sum = 0.0;
double f = 1.0;
for (unsigned int i = 1;; ++i) {
f *= (0.5 * x / i);
double f2 = f * f;
if (f2 <= sum * EPS) break;
sum += f2;
}
return 1.0 + sum;
}
void KaizerWindow::windowedSinc(FIRCoefficient kernel[], const unsigned int order, const double fc, const double beta, const double amp) {
const double fc_pi = M_PI * fc;
const double recipOrder = 1.0 / order;
const double mult = 2.0 * fc * amp / bessel(beta);
for (int i = order, j = 0; 0 <= i; i -= 2, ++j) {
double xw = i * recipOrder;
double win = bessel(beta * sqrt(fabs(1.0 - xw * xw)));
double xs = i * fc_pi;
double sinc = (i == 0) ? 1.0 : sin(xs) / xs;
FIRCoefficient imp = FIRCoefficient(mult * sinc * win);
kernel[j] = imp;
kernel[order - j] = imp;
}
}
ResamplerStage *SincResampler::createSincResampler(const double inputFrequency, const double outputFrequency, const double passbandFrequency, const double stopbandFrequency, const double dbSNR, const unsigned int maxUpsampleFactor) {
unsigned int upsampleFactor;
double downsampleFactor;
computeResampleFactors(upsampleFactor, downsampleFactor, inputFrequency, outputFrequency, maxUpsampleFactor);
double baseSamplePeriod = 1.0 / (inputFrequency * upsampleFactor);
double fp = passbandFrequency * baseSamplePeriod;
double fs = stopbandFrequency * baseSamplePeriod;
double fc = 0.5 * (fp + fs);
double beta = KaizerWindow::estimateBeta(dbSNR);
unsigned int order = KaizerWindow::estimateOrder(dbSNR, fp, fs);
const unsigned int kernelLength = order + 1;
#ifdef SRCTOOLS_SINC_RESAMPLER_DEBUG_LOG
std::clog << "FIR: " << upsampleFactor << "/" << downsampleFactor << ", N=" << kernelLength << ", NPh=" << kernelLength / double(upsampleFactor) << ", C=" << 0.5 / fc << ", fp=" << fp << ", fs=" << fs << ", M=" << maxUpsampleFactor << std::endl;
#endif
FIRCoefficient *windowedSincKernel = new FIRCoefficient[kernelLength];
KaizerWindow::windowedSinc(windowedSincKernel, order, fc, beta, upsampleFactor);
ResamplerStage *windowedSincStage = new FIRResampler(upsampleFactor, downsampleFactor, windowedSincKernel, kernelLength);
delete[] windowedSincKernel;
return windowedSincStage;
}

View File

@@ -15,12 +15,23 @@ FILE *allog;
#ifdef USE_OPENAL
ALuint buffers[4]; /* front and back buffers */
ALuint buffers_cd[4]; /* front and back buffers */
static ALuint source[2]; /* audio source */
ALuint buffers_midi[4]; /* front and back buffers */
static ALuint source[3]; /* audio source */
#endif
#define FREQ 48000
#define BUFLEN SOUNDBUFLEN
static int midi_freq = 44100;
static int midi_buf_size = 4410;
static int initialized = 0;
void al_set_midi(int freq, int buf_size)
{
midi_freq = freq;
midi_buf_size = buf_size;
}
void closeal(void);
ALvoid alutInit(ALint *argc,ALbyte **argv)
{
@@ -64,28 +75,41 @@ void initalmain(int argc, char *argv[])
void closeal(void)
{
if (!initialized) return;
#ifdef USE_OPENAL
alutExit();
#endif
initialized = 0;
}
void inital(ALvoid)
{
if (initialized) return;
#ifdef USE_OPENAL
int c;
float buf[BUFLEN*2];
float *buf = NULL, *cd_buf = NULL, *midi_buf = NULL;
int16_t *buf_int16 = NULL, *cd_buf_int16 = NULL, *midi_buf_int16 = NULL;
float cd_buf[CD_BUFLEN*2];
int16_t buf_int16[BUFLEN*2];
int16_t cd_buf_int16[CD_BUFLEN*2];
if (sound_is_float)
{
buf = (float *) malloc((BUFLEN << 1) * sizeof(float));
cd_buf = (float *) malloc((CD_BUFLEN << 1) * sizeof(float));
midi_buf = (float *) malloc(midi_buf_size * sizeof(float));
}
else
{
buf_int16 = (int16_t *) malloc((BUFLEN << 1) * sizeof(int16_t));
cd_buf_int16 = (int16_t *) malloc((CD_BUFLEN << 1) * sizeof(int16_t));
midi_buf_int16 = (int16_t *) malloc(midi_buf_size * sizeof(int16_t));
}
alGenBuffers(4, buffers);
alGenBuffers(4, buffers_cd);
alGenBuffers(4, buffers_midi);
alGenSources(2, source);
alGenSources(3, source);
alSource3f(source[0], AL_POSITION, 0.0, 0.0, 0.0);
alSource3f(source[0], AL_VELOCITY, 0.0, 0.0, 0.0);
@@ -97,9 +121,24 @@ void inital(ALvoid)
alSource3f(source[1], AL_DIRECTION, 0.0, 0.0, 0.0);
alSourcef (source[1], AL_ROLLOFF_FACTOR, 0.0 );
alSourcei (source[1], AL_SOURCE_RELATIVE, AL_TRUE );
alSource3f(source[2], AL_POSITION, 0.0, 0.0, 0.0);
alSource3f(source[2], AL_VELOCITY, 0.0, 0.0, 0.0);
alSource3f(source[2], AL_DIRECTION, 0.0, 0.0, 0.0);
alSourcef (source[2], AL_ROLLOFF_FACTOR, 0.0 );
alSourcei (source[2], AL_SOURCE_RELATIVE, AL_TRUE );
memset(buf,0,BUFLEN*2*sizeof(float));
memset(cd_buf,0,BUFLEN*2*sizeof(float));
if (sound_is_float)
{
memset(buf,0,BUFLEN*2*sizeof(float));
memset(cd_buf,0,BUFLEN*2*sizeof(float));
memset(midi_buf,0,midi_buf_size*sizeof(float));
}
else
{
memset(buf_int16,0,BUFLEN*2*sizeof(int16_t));
memset(cd_buf_int16,0,BUFLEN*2*sizeof(int16_t));
memset(midi_buf_int16,0,midi_buf_size*sizeof(int16_t));
}
for (c = 0; c < 4; c++)
{
@@ -107,123 +146,84 @@ void inital(ALvoid)
{
alBufferData(buffers[c], AL_FORMAT_STEREO_FLOAT32, buf, BUFLEN*2*sizeof(float), FREQ);
alBufferData(buffers_cd[c], AL_FORMAT_STEREO_FLOAT32, cd_buf, CD_BUFLEN*2*sizeof(float), CD_FREQ);
alBufferData(buffers_midi[c], AL_FORMAT_STEREO_FLOAT32, midi_buf, midi_buf_size*sizeof(float), midi_freq);
}
else
{
alBufferData(buffers[c], AL_FORMAT_STEREO16, buf_int16, BUFLEN*2*sizeof(int16_t), FREQ);
alBufferData(buffers_cd[c], AL_FORMAT_STEREO16, cd_buf_int16, CD_BUFLEN*2*sizeof(int16_t), CD_FREQ);
alBufferData(buffers_midi[c], AL_FORMAT_STEREO16, midi_buf_int16, midi_buf_size*sizeof(int16_t), midi_freq);
}
}
alSourceQueueBuffers(source[0], 4, buffers);
alSourceQueueBuffers(source[1], 4, buffers_cd);
alSourceQueueBuffers(source[2], 4, buffers_midi);
alSourcePlay(source[0]);
alSourcePlay(source[1]);
alSourcePlay(source[2]);
if (sound_is_float)
{
free(midi_buf);
free(cd_buf);
free(buf);
}
else
{
free(midi_buf_int16);
free(cd_buf_int16);
free(buf_int16);
}
initialized = 1;
#endif
}
void givealbuffer(float *buf)
void givealbuffer_common(void *buf, uint8_t src, int size, int freq)
{
#ifdef USE_OPENAL
int processed;
int state;
ALuint buffer;
alGetSourcei(source[0], AL_SOURCE_STATE, &state);
alGetSourcei(source[src], AL_SOURCE_STATE, &state);
if (state==0x1014)
{
alSourcePlay(source[0]);
alSourcePlay(source[src]);
}
alGetSourcei(source[0], AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(source[src], AL_BUFFERS_PROCESSED, &processed);
if (processed>=1)
{
alSourceUnqueueBuffers(source[0], 1, &buffer);
alSourceUnqueueBuffers(source[src], 1, &buffer);
alBufferData(buffer, AL_FORMAT_STEREO_FLOAT32, buf, BUFLEN*2*sizeof(float), FREQ);
if (sound_is_float)
{
alBufferData(buffer, AL_FORMAT_STEREO_FLOAT32, buf, size * sizeof(float), freq);
}
else
{
alBufferData(buffer, AL_FORMAT_STEREO16, buf, size * sizeof(int16_t), freq);
}
alSourceQueueBuffers(source[0], 1, &buffer);
alSourceQueueBuffers(source[src], 1, &buffer);
}
#endif
}
void givealbuffer_int16(int16_t *buf)
void givealbuffer(void *buf)
{
#ifdef USE_OPENAL
int processed;
int state;
ALuint buffer;
alGetSourcei(source[0], AL_SOURCE_STATE, &state);
if (state==0x1014)
{
alSourcePlay(source[0]);
}
alGetSourcei(source[0], AL_BUFFERS_PROCESSED, &processed);
if (processed>=1)
{
alSourceUnqueueBuffers(source[0], 1, &buffer);
alBufferData(buffer, AL_FORMAT_STEREO16, buf, BUFLEN*2*sizeof(int16_t), FREQ);
alSourceQueueBuffers(source[0], 1, &buffer);
}
#endif
givealbuffer_common(buf, 0, BUFLEN << 1, FREQ);
}
void givealbuffer_cd(float *buf)
void givealbuffer_cd(void *buf)
{
#ifdef USE_OPENAL
int processed;
int state;
alGetSourcei(source[1], AL_SOURCE_STATE, &state);
if (state==0x1014)
{
alSourcePlay(source[1]);
}
alGetSourcei(source[1], AL_BUFFERS_PROCESSED, &processed);
if (processed>=1)
{
ALuint buffer;
alSourceUnqueueBuffers(source[1], 1, &buffer);
alBufferData(buffer, AL_FORMAT_STEREO_FLOAT32, buf, CD_BUFLEN*2*sizeof(float), CD_FREQ);
alSourceQueueBuffers(source[1], 1, &buffer);
}
#endif
givealbuffer_common(buf, 1, CD_BUFLEN << 1, CD_FREQ);
}
void givealbuffer_cd_int16(int16_t *buf)
void givealbuffer_midi(void *buf, uint32_t size)
{
#ifdef USE_OPENAL
int processed;
int state;
alGetSourcei(source[1], AL_SOURCE_STATE, &state);
if (state==0x1014)
{
alSourcePlay(source[1]);
}
alGetSourcei(source[1], AL_BUFFERS_PROCESSED, &processed);
if (processed>=1)
{
ALuint buffer;
alSourceUnqueueBuffers(source[1], 1, &buffer);
alBufferData(buffer, AL_FORMAT_STEREO16, buf, CD_BUFLEN*2*sizeof(int16_t), CD_FREQ);
alSourceQueueBuffers(source[1], 1, &buffer);
}
#endif
givealbuffer_common(buf, 2, size, midi_freq);
}

View File

@@ -8,7 +8,7 @@
*
* Roland MPU-401 emulation.
*
* Version: @(#)sound_mpu401.c 1.0.0 2017/05/30
* Version: @(#)sound_mpu401.c 1.0.1 2017/06/19
*
* Author: Sarah Walker, <http://pcem-emulator.co.uk/>
* DOSBox Team,
@@ -25,7 +25,7 @@
#include "../io.h"
#include "../pic.h"
#include "../timer.h"
#include "../win/plat_midi.h" /*YUCK*/
#include "midi.h"
#include "sound.h"
#include "snd_mpu401.h"

View File

@@ -9,9 +9,9 @@
#include "../io.h"
#include "../pic.h"
#include "../dma.h"
#include "../win/plat_midi.h" /*YUCK*/
#include "../timer.h"
#include "../device.h"
#include "midi.h"
#include "sound.h"
#include "snd_mpu401.h"
#include "snd_sb_dsp.h"

View File

@@ -23,6 +23,7 @@
#include "../timer.h"
#include "../cdrom.h"
#include "../win/plat_thread.h"
#include "midi.h"
#include "sound.h"
#include "snd_opl.h"
#include "snd_adlib.h"
@@ -262,7 +263,7 @@ static void sound_cd_thread(void *param)
}
else
{
givealbuffer_cd_int16(cd_out_buffer_int16);
givealbuffer_cd(cd_out_buffer_int16);
}
}
}
@@ -275,10 +276,6 @@ static int cd_thread_enable = 0;
void sound_realloc_buffers(void)
{
closeal();
initalmain(0,NULL);
inital();
if (outbuffer_ex != NULL)
{
free(outbuffer_ex);
@@ -341,7 +338,9 @@ void sound_add_handler(void (*get_buffer)(int32_t *buffer, int len, void *p), vo
void sound_poll(void *priv)
{
sound_poll_time += sound_poll_latch;
midi_poll();
sound_pos_global++;
if (sound_pos_global == SOUNDBUFLEN)
{
@@ -378,7 +377,7 @@ void sound_poll(void *priv)
}
else
{
givealbuffer_int16(outbuffer_ex_int16);
givealbuffer(outbuffer_ex_int16);
}
}

View File

@@ -46,7 +46,5 @@ void sound_cd_thread_reset();
void closeal(void);
void initalmain(int argc, char *argv[]);
void inital();
void givealbuffer(float *buf);
void givealbuffer_int16(int16_t *buf);
void givealbuffer_cd(float *buf);
void givealbuffer_cd_int16(int16_t *buf);
void givealbuffer(void *buf);
void givealbuffer_cd(void *buf);

View File

@@ -44,13 +44,47 @@ MAINMENU MENU DISCARDABLE
BEGIN
POPUP "&Action"
BEGIN
MENUITEM "Take s&creenshot\tCtrl+F11", IDM_ACTION_SCREENSHOT
MENUITEM SEPARATOR
MENUITEM "&Hard Reset", IDM_ACTION_HRESET
MENUITEM "&Ctrl+Alt+Del\tCtrl+F12", IDM_ACTION_RESET_CAD
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_ACTION_EXIT
END
POPUP "&View"
BEGIN
MENUITEM "&Resizeable window", IDM_VID_RESIZE
MENUITEM "R&emember size && position", IDM_VID_REMEMBER
MENUITEM SEPARATOR
POPUP "Re&nderer"
BEGIN
MENUITEM "&DirectDraw", IDM_VID_DDRAW
MENUITEM "Direct&3D 9", IDM_VID_D3D
END
MENUITEM SEPARATOR
POPUP "&Window scale factor"
BEGIN
MENUITEM "&0.5x", IDM_VID_SCALE_1X
MENUITEM "&1x", IDM_VID_SCALE_2X
MENUITEM "1.&5x", IDM_VID_SCALE_3X
MENUITEM "&2x", IDM_VID_SCALE_4X
END
MENUITEM SEPARATOR
MENUITEM "&Fullscreen\tCtrl+Alt+PageUP", IDM_VID_FULLSCREEN
POPUP "Fullscreen &stretch mode"
BEGIN
MENUITEM "&Full screen stretch", IDM_VID_FS_FULL
MENUITEM "&4:3", IDM_VID_FS_43
MENUITEM "&Square pixels", IDM_VID_FS_SQ
MENUITEM "&Integer scale", IDM_VID_FS_INT
END
POPUP "E&GA/(S)VGA settings"
BEGIN
MENUITEM "&Inverted VGA monitor", IDM_VID_INVERT
MENUITEM "E&GA/(S)VGA overscan", IDM_VID_OVERSCAN
END
MENUITEM SEPARATOR
MENUITEM "F&orce 4:3 display ratio", IDM_VID_FORCE43
MENUITEM "Change contrast for &monochrome display", IDM_VID_CGACON
END
POPUP "&Tools"
BEGIN
MENUITEM "&Settings...", IDM_CONFIG
@@ -58,38 +92,8 @@ BEGIN
MENUITEM "&Load configuration...", IDM_CONFIG_LOAD
MENUITEM "&Save configuration...", IDM_CONFIG_SAVE
MENUITEM SEPARATOR
POPUP "&Video"
BEGIN
MENUITEM "&Resizeable window", IDM_VID_RESIZE
MENUITEM "R&emember size && position", IDM_VID_REMEMBER
MENUITEM SEPARATOR
MENUITEM "&DirectDraw", IDM_VID_DDRAW
MENUITEM "Direct&3D 9", IDM_VID_D3D
MENUITEM SEPARATOR
POPUP "&Window scale factor"
BEGIN
MENUITEM "&0.5x", IDM_VID_SCALE_1X
MENUITEM "&1x", IDM_VID_SCALE_2X
MENUITEM "1.&5x", IDM_VID_SCALE_3X
MENUITEM "&2x", IDM_VID_SCALE_4X
END
MENUITEM SEPARATOR
MENUITEM "&Fullscreen\tCtrl+Alt+PageUP", IDM_VID_FULLSCREEN
POPUP "Fullscreen &stretch mode"
BEGIN
MENUITEM "&Full screen stretch", IDM_VID_FS_FULL
MENUITEM "&4:3", IDM_VID_FS_43
MENUITEM "&Square pixels", IDM_VID_FS_SQ
MENUITEM "&Integer scale", IDM_VID_FS_INT
END
MENUITEM SEPARATOR
MENUITEM "&Inverted VGA monitor", IDM_VID_INVERT
MENUITEM "F&orce 4:3 display ratio", IDM_VID_FORCE43
MENUITEM "E&GA/(S)VGA overscan", IDM_VID_OVERSCAN
MENUITEM "Change contrast for &monochrome display", IDM_VID_CGACON
END
MENUITEM "S&tatus", IDM_STATUS
MENUITEM "Take s&creenshot\tCtrl+F11", IDM_ACTION_SCREENSHOT
END
#if defined(ENABLE_LOG_TOGGLES) || defined(ENABLE_LOG_COMMANDS)
POPUP "&Logging"
@@ -211,12 +215,15 @@ BEGIN
CONTROL "List2",IDC_SETTINGSCATLIST,"SysListView32",LVS_LIST |
LVS_SHOWSELALWAYS | LVS_SINGLESEL | WS_BORDER | WS_TABSTOP,7,7,90,197
CONTROL "",-1,"Static",SS_BLACKFRAME | SS_SUNKEN,1,211,363,1
/* Leave this commented out until we get into localization. */
#if 0
LTEXT "Language:",IDT_1700,7,222,41,10
COMBOBOX IDC_COMBO_LANG,48,221,108,120,CBS_DROPDOWN | WS_VSCROLL |
WS_TABSTOP
#endif
END
DLG_CFG_MACHINE DIALOG DISCARDABLE 97, 0, 267, 112
DLG_CFG_MACHINE DIALOG DISCARDABLE 97, 0, 267, 132
STYLE DS_CONTROL | WS_CHILD
FONT 9, "Segoe UI"
BEGIN
@@ -227,24 +234,27 @@ BEGIN
COMBOBOX IDC_COMBO_CPU_TYPE,71,25,45,120,CBS_DROPDOWNLIST |
WS_VSCROLL | WS_TABSTOP
LTEXT "CPU type:",IDT_1702,7,26,59,10
COMBOBOX IDC_COMBO_WS,71,44,189,120,CBS_DROPDOWNLIST | WS_VSCROLL |
WS_TABSTOP
LTEXT "Wait states:",IDT_1703,7,45,60,10
COMBOBOX IDC_COMBO_CPU,145,25,115,120,CBS_DROPDOWNLIST |
WS_VSCROLL | WS_TABSTOP
LTEXT "CPU:",IDT_1704,124,26,18,10
CONTROL "Dynamic Recompiler",IDC_CHECK_DYNAREC,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,7,80,94,10
CONTROL "Enable FPU",IDC_CHECK_FPU,"Button",BS_AUTOCHECKBOX |
WS_TABSTOP,147,80,113,10
COMBOBOX IDC_COMBO_WS,71,44,189,120,CBS_DROPDOWNLIST | WS_VSCROLL |
WS_TABSTOP
LTEXT "Wait states:",IDT_1703,7,45,60,10
EDITTEXT IDC_MEMTEXT,70,63,45,12,ES_AUTOHSCROLL | ES_NUMBER
CONTROL "",IDC_MEMSPIN,"msctls_updown32",UDS_SETBUDDYINT |
UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS,113,63,
12,12
LTEXT "MB",IDT_1705,123,64,10,10
LTEXT "Memory:",IDT_1706,7,64,30,10
LTEXT "NVR Path:",IDT_1700,7,83,60,10
EDITTEXT IDC_EDIT_NVR_PATH,71,82,138,12
PUSHBUTTON "&Specify...",IDC_BUTTON_NVR_PATH,214,82,46,12
CONTROL "Dynamic Recompiler",IDC_CHECK_DYNAREC,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,7,100,94,10
CONTROL "Enable FPU",IDC_CHECK_FPU,"Button",BS_AUTOCHECKBOX |
WS_TABSTOP,147,100,113,10
CONTROL "Enable time sync",IDC_CHECK_SYNC,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,7,95,102,10
BS_AUTOCHECKBOX | WS_TABSTOP,7,115,102,10
END
DLG_CFG_VIDEO DIALOG DISCARDABLE 97, 0, 267, 63
@@ -287,9 +297,11 @@ BEGIN
WS_TABSTOP
LTEXT "Sound card:",IDT_1711,7,8,59,10
PUSHBUTTON "Configure",IDC_CONFIGURE_SND,214,7,46,12
COMBOBOX IDC_COMBO_MIDI,71,25,189,120,CBS_DROPDOWNLIST | WS_VSCROLL |
COMBOBOX IDC_COMBO_MIDI,71,25,140,120,CBS_DROPDOWNLIST | WS_VSCROLL |
WS_TABSTOP
LTEXT "MIDI Out Device:",IDT_1712,7,26,59,10
PUSHBUTTON "Configure",IDC_CONFIGURE_MIDI,214,25,46,12
CONTROL "Standalone MPU-401",IDC_CHECK_MPU401,"Button",
BS_AUTOCHECKBOX | WS_TABSTOP,7,45,199,10
@@ -868,10 +880,12 @@ BEGIN
IDS_2221 "Turbo"
IDS_2222 "On"
IDS_2223 "Off"
IDS_2224 "<Placeholder string>"
IDS_2225 "English (United States)"
IDS_2224 "Logitech 3-button mouse (serial)"
IDS_2225 "Specify the NVR Path"
IDS_2226 "<Placeholder string>"
IDS_2227 "English (United States)"
END
#define IDS_LANG_ENUS IDS_2225
#define IDS_LANG_ENUS IDS_2227
#ifndef _MAC

View File

@@ -1,10 +1,7 @@
/* Copyright holders: Sarah Walker
see COPYING for more details
*/
void midi_init();
void midi_close();
void midi_write(uint8_t val);
int midi_get_num_devs();
void midi_get_dev_name(int num, char *s);
extern int midi_id;
void plat_midi_init();
void plat_midi_close();
void plat_midi_play_msg(uint8_t* val);
void plat_midi_play_sysex(uint8_t* data, unsigned int len);
int plat_midi_write(uint8_t val);
int plat_midi_get_num_devs();
void plat_midi_get_dev_name(int num, char *s);

2
src/WIN/plat_ticks.h Normal file
View File

@@ -0,0 +1,2 @@
uint32_t get_ticks(void);
void delay_ms(uint32_t count);

View File

@@ -87,7 +87,10 @@
#define IDC_SETTINGSCATLIST 1001 /* generic config */
#define IDC_CFILE 1002 /* Select File dialog */
#define IDC_CHECK_SYNC 1008
/* Leave this as is until we finally get into localization in 86Box 3.00(?). */
#if 0
#define IDC_COMBO_LANG 1009
#endif
#define IDC_COMBO_MACHINE 1010 /* machine/cpu config */
#define IDC_CONFIGURE_MACHINE 1011
@@ -99,6 +102,8 @@
#define IDC_MEMTEXT 1017
#define IDC_MEMSPIN 1018
#define IDC_TEXT_MB IDT_1705
#define IDC_EDIT_NVR_PATH 1019
#define IDC_BUTTON_NVR_PATH 1020
#define IDC_VIDEO 1030 /* video config */
#define IDC_COMBO_VIDEO 1031
@@ -181,6 +186,7 @@
#define IDC_CONFIGURE_BUSLOGIC 1205
#define IDC_CONFIGURE_PCAP 1206
#define IDC_CONFIGURE_NET 1207
#define IDC_CONFIGURE_MIDI 1208
#define IDC_JOY1 1210
#define IDC_JOY2 1211
#define IDC_JOY3 1212
@@ -367,11 +373,13 @@
#define IDS_2221 2221 // "Turbo"
#define IDS_2222 2222 // "On"
#define IDS_2223 2223 // "Off"
#define IDS_2224 2224 // "<Placeholder string>"
#define IDS_2225 2225 // "English (United States)"
#define IDS_2224 2224 // "Logitech 3-button mouse (serial)"
#define IDS_2225 2225 // "Specify the NVR Path"
#define IDS_2226 2226 // "<Placeholder string>"
#define IDS_2227 2227 // "English (United States)"
#define IDS_LANG_ENUS IDS_2225
#define STRINGS_NUM 178
#define IDS_LANG_ENUS IDS_2227
#define STRINGS_NUM 180
#define IDM_ABOUT 40001

View File

@@ -49,6 +49,7 @@
#include "plat_mouse.h"
#include "plat_midi.h"
#include "plat_thread.h"
#include "plat_ticks.h"
#include "plat_ui.h"
#include "win.h"
@@ -271,6 +272,16 @@ void leave_fullscreen(void)
leave_fullscreen_flag = 1;
}
uint32_t get_ticks(void)
{
return GetTickCount();
}
void delay_ms(uint32_t count)
{
Sleep(count);
}
void mainthread(LPVOID param)
{
int frames = 0;
@@ -2044,6 +2055,7 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
null_close(i);
}
}
resetpchard_close();
loadconfig(wopenfilestring);
for (i = 0; i < CDROM_NUM; i++)
{
@@ -2075,7 +2087,7 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
mem_resize();
loadbios();
update_status_bar_panes(hwndStatus);
resetpchard();
resetpchard_init();
}
}
pause = 0;
@@ -2318,7 +2330,7 @@ LRESULT CALLBACK StatusBarProcedure(HWND hwnd, UINT message, WPARAM wParam, LPAR
break;
}
ret = file_dlg_w_st(hwnd, IDS_2173, discfns[id], id);
ret = file_dlg_w_st(hwnd, IDS_2173, discfns[id], 0);
if (!ret)
{
disc_close(id);

View File

@@ -8,7 +8,7 @@
*
* Windows device configuration dialog implementation.
*
* Version: @(#)win_deviceconfig.c 1.0.0 2017/05/30
* Version: @(#)win_deviceconfig.c 1.0.1 2017/06/19
*
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
* Miran Grca, <mgrca8@gmail.com>
@@ -18,8 +18,10 @@
#include "../ibm.h"
#include "../config.h"
#include "../device.h"
#include "plat_midi.h"
#define NO_UNICODE /*FIXME: not Unicode? */
#include "win.h"
#include "win_language.h"
#include <windowsx.h>
@@ -29,15 +31,22 @@ static device_t *config_device;
static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND h;
int val_int;
int ret;
int id;
device_config_t *config;
int c;
int num;
int changed;
char s[80];
switch (message)
{
case WM_INITDIALOG:
{
int id = IDC_CONFIG_BASE;
device_config_t *config = config_device->config;
int c;
id = IDC_CONFIG_BASE;
config = config_device->config;
while (config->type != -1)
{
@@ -70,6 +79,21 @@ static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam
id += 2;
break;
case CONFIG_MIDI:
val_int = config_get_int(config_device->name, config->name, config->default_int);
num = plat_midi_get_num_devs();
for (c = 0; c < num; c++)
{
plat_midi_get_dev_name(c, s);
SendMessage(h, CB_ADDSTRING, 0, (LPARAM)(LPCSTR)s);
if (val_int == c)
SendMessage(h, CB_SETCURSEL, c, 0);
}
id += 2;
break;
case CONFIG_HEX16:
val_int = config_get_hex16(config_device->name, config->name, config->default_int);
@@ -112,10 +136,9 @@ static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam
{
case IDOK:
{
int id = IDC_CONFIG_BASE;
device_config_t *config = config_device->config;
int c;
int changed = 0;
id = IDC_CONFIG_BASE;
config = config_device->config;
changed = 0;
while (config->type != -1)
{
@@ -147,6 +170,17 @@ static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam
id += 2;
break;
case CONFIG_MIDI:
val_int = config_get_int(config_device->name, config->name, config->default_int);
c = SendMessage(h, CB_GETCURSEL, 0, 0);
if (val_int != c)
changed = 1;
id += 2;
break;
case CONFIG_HEX16:
val_int = config_get_hex16(config_device->name, config->name, config->default_int);
@@ -183,11 +217,17 @@ static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam
EndDialog(hdlg, 0);
return TRUE;
}
if (MessageBox(NULL, "This will reset 86Box!\nOkay to continue?", "86Box", MB_OKCANCEL) != IDOK)
{
EndDialog(hdlg, 0);
return TRUE;
ret = msgbox_reset(ghwnd);
switch(ret)
{
case IDNO:
EndDialog(hdlg, 0);
return TRUE;
case IDCANCEL:
return FALSE;
default:
break;
}
id = IDC_CONFIG_BASE;
@@ -215,6 +255,13 @@ static BOOL CALLBACK deviceconfig_dlgproc(HWND hdlg, UINT message, WPARAM wParam
id += 2;
break;
case CONFIG_MIDI:
c = SendMessage(h, CB_GETCURSEL, 0, 0);
config_set_int(config_device->name, config->name, c);
id += 2;
break;
case CONFIG_HEX16:
c = SendMessage(h, CB_GETCURSEL, 0, 0);
for (; c > 0; c--)
@@ -308,6 +355,7 @@ void deviceconfig_open(HWND hwnd, device_t *device)
break;
case CONFIG_SELECTION:
case CONFIG_MIDI:
case CONFIG_HEX16:
case CONFIG_HEX20:
/*Combo box*/

View File

@@ -237,10 +237,10 @@ static int CALLBACK BrowseCallbackProc(HWND hwnd,UINT uMsg, LPARAM lParam, LPARA
WCHAR path[MAX_PATH];
wchar_t *BrowseFolder(wchar_t *saved_path)
wchar_t *BrowseFolder(wchar_t *saved_path, wchar_t *title)
{
BROWSEINFO bi = { 0 };
bi.lpszTitle = L"Browse for folder...";
bi.lpszTitle = title;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) saved_path;
@@ -253,15 +253,12 @@ wchar_t *BrowseFolder(wchar_t *saved_path)
SHGetPathFromIDList(pidl, path);
/* Free memory used. */
#if 0
IMalloc *imalloc = 0;
if (SUCCEEDED(SHGetMalloc(&imalloc)))
{
imalloc->Free(pidl);
imalloc->Release();
imalloc->lpVtbl->Free(imalloc, pidl);
imalloc->lpVtbl->Release(imalloc);
}
#endif
free(pidl);
return path;
}

View File

@@ -46,6 +46,8 @@ void win_language_check();
LPTSTR win_language_get_string_from_id(int i);
LPTSTR win_language_get_string_from_string(char *str);
wchar_t *BrowseFolder(wchar_t *saved_path, wchar_t *title);
#ifdef __cplusplus
}
#endif

View File

@@ -1,25 +1,8 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* MIDI interface to host device.
*
* Version: @(#)win_midi.c 1.0.0 2017/05/30
*
* Author: Sarah Walker, <http://pcem-emulator.co.uk/>
* Miran Grca, <mgrca8@gmail.com>
* Copyright 2008-2017 Sarah Walker.
* Copyright 2016-2017 Miran Grca.
*/
#include <windows.h>
#include <mmsystem.h>
#include "../ibm.h"
#include "../config.h"
#include "../SOUND/midi.h"
#include "plat_midi.h"
int midi_id = 0;
@@ -27,8 +10,6 @@ static HMIDIOUT midi_out_device = NULL;
HANDLE m_event;
void midi_close();
static uint8_t midi_rt_buf[1024];
static uint8_t midi_cmd_buf[1024];
static int midi_cmd_pos = 0;
@@ -37,31 +18,20 @@ static uint8_t midi_status = 0;
static unsigned int midi_sysex_start = 0;
static unsigned int midi_sysex_delay = 0;
uint8_t MIDI_evt_len[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x10
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x30
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x70
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x80
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0x90
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xa0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xb0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xc0
2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // 0xd0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xe0
0,2,3,2, 0,0,1,0, 1,0,1,1, 1,0,1,0 // 0xf0
};
void midi_init()
void plat_midi_init()
{
/* This is for compatibility with old configuration files. */
midi_id = config_get_int("Sound", "midi_host_device", -1);
if (midi_id == -1)
{
midi_id = config_get_int(SYSTEM_MIDI_NAME, "midi", 0);
}
else
{
config_delete_var("Sound", "midi_host_device");
config_set_int(SYSTEM_MIDI_NAME, "midi", midi_id);
}
MMRESULT hr = MMSYSERR_NOERROR;
memset(midi_rt_buf, 0, sizeof(midi_rt_buf));
@@ -86,11 +56,11 @@ void midi_init()
return;
}
}
midiOutReset(midi_out_device);
}
void midi_close()
void plat_midi_close()
{
if (midi_out_device != NULL)
{
@@ -101,11 +71,11 @@ void midi_close()
}
}
int midi_get_num_devs()
int plat_midi_get_num_devs()
{
return midiOutGetNumDevs();
}
void midi_get_dev_name(int num, char *s)
void plat_midi_get_dev_name(int num, char *s)
{
MIDIOUTCAPS caps;
@@ -113,17 +83,14 @@ void midi_get_dev_name(int num, char *s)
strcpy(s, caps.szPname);
}
static int midi_pos;
static uint8_t midi_sysex_data[1024+2];
void PlayMsg(uint8_t *msg)
void plat_midi_play_msg(uint8_t *msg)
{
midiOutShortMsg(midi_out_device, *(uint32_t *) msg);
}
MIDIHDR m_hdr;
void PlaySysex(uint8_t *sysex, unsigned int len)
void plat_midi_play_sysex(uint8_t *sysex, unsigned int len)
{
MMRESULT result;
@@ -152,124 +119,7 @@ void PlaySysex(uint8_t *sysex, unsigned int len)
}
}
#define SYSEX_SIZE 1024
#define RAWBUF 1024
void midi_write(uint8_t val)
int plat_midi_write(uint8_t val)
{
uint32_t passed_ticks;
if (midi_sysex_start)
{
passed_ticks = GetTickCount() - midi_sysex_start;
if (passed_ticks < midi_sysex_delay)
{
Sleep(midi_sysex_delay - passed_ticks);
}
}
/* Test for a realtime MIDI message */
if (val >= 0xf8)
{
midi_rt_buf[0] = val;
PlayMsg(midi_rt_buf);
return;
}
/* Test for a active sysex transfer */
if (midi_status == 0xf0)
{
if (!(val & 0x80))
{
if (midi_pos < (SYSEX_SIZE-1)) midi_sysex_data[midi_pos++] = val;
return;
}
else
{
midi_sysex_data[midi_pos++] = 0xf7;
if ((midi_sysex_start) && (midi_pos >= 4) && (midi_pos <= 9) && (midi_sysex_data[1] == 0x411) && (midi_sysex_data[3] == 0x16))
{
/* pclog("MIDI: Skipping invalid MT-32 SysEx MIDI message\n"); */
}
else
{
PlaySysex(midi_sysex_data, midi_pos);
if (midi_sysex_start)
{
if (midi_sysex_data[5] == 0x7f)
{
midi_sysex_delay = 290; /* All parameters reset */
}
else if ((midi_sysex_data[5] == 0x10) && (midi_sysex_data[6] == 0x00) && (midi_sysex_data[7] == 0x04))
{
midi_sysex_delay = 145; /* Viking Child */
}
else if ((midi_sysex_data[5] == 0x10) && (midi_sysex_data[6] == 0x00) && (midi_sysex_data[7] == 0x01))
{
midi_sysex_delay = 30; /* Dark Sun 1 */
}
else
midi_sysex_delay = (unsigned int) (((float) (midi_pos) * 1.25f) * 1000.0f / 3125.0f) + 2;
midi_sysex_start = GetTickCount();
}
}
}
}
if (val & 0x80)
{
midi_status = val;
midi_cmd_pos = 0;
midi_cmd_len = MIDI_evt_len[val];
if (midi_status == 0xf0)
{
midi_sysex_data[0] = 0xf0;
midi_pos = 1;
}
}
if (midi_cmd_len)
{
midi_cmd_buf[midi_cmd_pos++] = val;
if (midi_cmd_pos >= midi_cmd_len)
{
PlayMsg(midi_cmd_buf);
midi_cmd_pos = 1;
}
}
}
void midi_reset()
{
uint8_t buf[64];
/* Flush buffers */
midiOutReset(midi_out_device);
/* GM1 reset */
buf[0] = 0xf0;
buf[1] = 0x7e;
buf[2] = 0x7f;
buf[3] = 0x09;
buf[4] = 0x01;
buf[5] = 0xf7;
PlaySysex((uint8_t *) buf, 6);
/* GS1 reset */
buf[0] = 0xf0;
buf[1] = 0x41;
buf[2] = 0x10;
buf[3] = 0x42;
buf[4] = 0x12;
buf[5] = 0x40;
buf[6] = 0x00;
buf[7] = 0x7f;
buf[8] = 0x00;
buf[9] = 0x41;
buf[10] = 0xf7;
PlaySysex((uint8_t *) buf, 11);
return 0;
}

View File

@@ -8,7 +8,7 @@
*
* Windows 86Box Settings dialog handler.
*
* Version: @(#)win_settings.c 1.0.3 2017/06/14
* Version: @(#)win_settings.c 1.0.4 2017/06/19
*
* Author: Miran Grca, <mgrca8@gmail.com>
* Copyright 2016-2017 Miran Grca.
@@ -35,6 +35,7 @@
#include "../scsi.h"
#include "../scsi_buslogic.h"
#include "../network/network.h"
#include "../sound/midi.h"
#include "../sound/sound.h"
#include "../sound/snd_dbopl.h"
#include "../sound/snd_mpu401.h"
@@ -49,6 +50,7 @@
/* Machine category */
static int temp_model, temp_cpu_m, temp_cpu, temp_wait_states, temp_mem_size, temp_dynarec, temp_fpu, temp_sync;
static wchar_t temp_nvr_path[520];
/* Video category */
static int temp_gfxcard, temp_video_speed, temp_voodoo;
@@ -57,7 +59,7 @@ static int temp_gfxcard, temp_video_speed, temp_voodoo;
static int temp_mouse, temp_joystick;
/* Sound category */
static int temp_sound_card, temp_midi_id, temp_mpu401, temp_SSI2001, temp_GAMEBLASTER, temp_GUS, temp_opl3_type;
static int temp_sound_card, temp_midi_device, temp_mpu401, temp_SSI2001, temp_GAMEBLASTER, temp_GUS, temp_opl3_type;
static int temp_float;
/* Network category */
@@ -87,6 +89,7 @@ static int displayed_category = 0;
extern int is486;
static int romstolist[ROM_MAX], listtomodel[ROM_MAX], romstomodel[ROM_MAX], modeltolist[ROM_MAX];
static int settings_sound_to_list[20], settings_list_to_sound[20];
static int settings_midi_to_list[20], settings_list_to_midi[20];
static int settings_mouse_to_list[20], settings_list_to_mouse[20];
static int settings_scsi_to_list[20], settings_list_to_scsi[20];
static int settings_network_to_list[20], settings_list_to_network[20];
@@ -104,6 +107,8 @@ static void win_settings_init(void)
temp_wait_states = cpu_waitstates;
temp_cpu = cpu;
temp_mem_size = mem_size;
memset(temp_nvr_path, 0, sizeof(temp_nvr_path));
wcscpy(temp_nvr_path, nvr_path);
temp_dynarec = cpu_use_dynarec;
temp_fpu = enable_external_fpu;
temp_sync = enable_sync;
@@ -119,7 +124,7 @@ static void win_settings_init(void)
/* Sound category */
temp_sound_card = sound_card_current;
temp_midi_id = midi_id;
temp_midi_device = midi_device_current;
temp_mpu401 = mpu401_standalone_enable;
temp_SSI2001 = SSI2001;
temp_GAMEBLASTER = GAMEBLASTER;
@@ -170,6 +175,7 @@ static int win_settings_changed(void)
i = i || (cpu_waitstates != temp_wait_states);
i = i || (cpu != temp_cpu);
i = i || (mem_size != temp_mem_size);
i = i || wcscmp(temp_nvr_path, nvr_path);
i = i || (temp_dynarec != cpu_use_dynarec);
i = i || (temp_fpu != enable_external_fpu);
i = i || (temp_sync != enable_sync);
@@ -185,7 +191,7 @@ static int win_settings_changed(void)
/* Sound category */
i = i || (sound_card_current != temp_sound_card);
i = i || (midi_id != temp_midi_id);
i = i || (midi_device_current != temp_midi_device);
i = i || (mpu401_standalone_enable != temp_mpu401);
i = i || (SSI2001 != temp_SSI2001);
i = i || (GAMEBLASTER != temp_GAMEBLASTER);
@@ -261,6 +267,8 @@ static void win_settings_save(void)
{
int i = 0;
resetpchard_close();
/* Machine category */
model = temp_model;
romset = model_getromset();
@@ -268,6 +276,8 @@ static void win_settings_save(void)
cpu_waitstates = temp_wait_states;
cpu = temp_cpu;
mem_size = temp_mem_size;
memset(nvr_path, 0, sizeof(nvr_path));
wcscpy(nvr_path, temp_nvr_path);
cpu_use_dynarec = temp_dynarec;
enable_external_fpu = temp_fpu;
enable_sync = temp_sync;
@@ -283,7 +293,7 @@ static void win_settings_save(void)
/* Sound category */
sound_card_current = temp_sound_card;
midi_id = temp_midi_id;
midi_device_current = temp_midi_device;
mpu401_standalone_enable = temp_mpu401;
SSI2001 = temp_SSI2001;
GAMEBLASTER = temp_GAMEBLASTER;
@@ -327,7 +337,7 @@ static void win_settings_save(void)
sound_realloc_buffers();
resetpchard();
resetpchard_init();
cpu_set();
@@ -493,7 +503,7 @@ static void win_settings_machine_recalc_model(HWND hdlg)
accel.nSec = 0;
accel.nInc = models[romstomodel[temp_romset]].ram_granularity;
SendMessage(h, UDM_SETACCEL, 1, (LPARAM)&accel);
if (!(models[romstomodel[temp_romset]].flags & MODEL_AT))
if (!(models[romstomodel[temp_romset]].flags & MODEL_AT) || (models[romstomodel[temp_romset]].ram_granularity >= 128))
{
SendMessage(h, UDM_SETPOS, 0, temp_mem_size);
h = GetDlgItem(hdlg, IDC_TEXT_MB);
@@ -517,6 +527,7 @@ static BOOL CALLBACK win_settings_machine_proc(HWND hdlg, UINT message, WPARAM w
int d = 0;
LPTSTR lptsTemp;
char *stransi;
wchar_t *p;
switch (message)
{
@@ -568,6 +579,9 @@ static BOOL CALLBACK win_settings_machine_proc(HWND hdlg, UINT message, WPARAM w
win_settings_machine_recalc_model(hdlg);
h = GetDlgItem(hdlg, IDC_EDIT_NVR_PATH);
SendMessage(h, WM_SETTEXT, 0, (LPARAM) temp_nvr_path);
free(lptsTemp);
return TRUE;
@@ -609,6 +623,24 @@ static BOOL CALLBACK win_settings_machine_proc(HWND hdlg, UINT message, WPARAM w
deviceconfig_open(hdlg, (void *)model_getdevice(temp_model));
break;
case IDC_BUTTON_NVR_PATH:
p = BrowseFolder(temp_nvr_path, win_language_get_string_from_id(IDS_2225));
if (wcscmp(p, L""))
{
memset(temp_nvr_path, 0, sizeof(temp_nvr_path));
wcscpy(temp_nvr_path, p);
if (temp_nvr_path[wcslen(temp_nvr_path) - 1] == L'/')
{
temp_nvr_path[wcslen(temp_nvr_path) - 1] = L'\\';
}
else if (temp_nvr_path[wcslen(temp_nvr_path) - 1] != L'\\')
{
temp_nvr_path[wcslen(temp_nvr_path)] = L'\\';
}
h = GetDlgItem(hdlg, IDC_EDIT_NVR_PATH);
SendMessage(h, WM_SETTEXT, 0, (LPARAM) temp_nvr_path);
}
break;
}
return FALSE;
@@ -892,7 +924,10 @@ static BOOL CALLBACK win_settings_input_proc(HWND hdlg, UINT message, WPARAM wPa
case 6: /* MouseSystems */
str_id = IDS_2140;
break;
case 7: /* Genius Bus */
case 7: /* Logitech Serial */
str_id = IDS_2224;
break;
case 8: /* Genius Bus */
str_id = IDS_2161;
break;
}
@@ -1114,8 +1149,6 @@ int find_irq_in_array(int irq, int def)
}
static char midi_dev_name_buf[512];
int mpu401_present(void)
{
char *n;
@@ -1134,9 +1167,10 @@ int mpu401_present(void)
int mpu401_standalone_allow(void)
{
char *n;
char *n, *md;
n = sound_card_get_internal_name(temp_sound_card);
md = midi_device_get_internal_name(temp_midi_device);
if (n != NULL)
{
if (!strcmp(n, "sb16") || !strcmp(n, "sbawe32"))
@@ -1145,6 +1179,14 @@ int mpu401_standalone_allow(void)
}
}
if (md != NULL)
{
if (!strcmp(md, "none"))
{
return 0;
}
}
return 1;
}
@@ -1154,8 +1196,8 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
int c = 0;
int d = 0;
LPTSTR lptsTemp;
device_t *sound_dev;
int num = 0;
device_t *sound_dev, *midi_dev;
char *s;
switch (message)
{
@@ -1166,7 +1208,7 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
c = d = 0;
while (1)
{
char *s = sound_card_getname(c);
s = sound_card_getname(c);
if (!s[0])
{
@@ -1210,15 +1252,50 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
}
h = GetDlgItem(hdlg, IDC_COMBO_MIDI);
num = midi_get_num_devs();
for (c = 0; c < num; c++)
c = d = 0;
while (1)
{
memset(midi_dev_name_buf, 0, 512);
midi_get_dev_name(c, midi_dev_name_buf);
mbstowcs(lptsTemp, midi_dev_name_buf, strlen(midi_dev_name_buf) + 1);
SendMessage(h, CB_ADDSTRING, 0, (LPARAM) lptsTemp);
if (c == temp_midi_id)
SendMessage(h, CB_SETCURSEL, c, 0);
s = midi_device_getname(c);
if (!s[0])
{
break;
}
settings_midi_to_list[c] = d;
if (midi_device_available(c))
{
midi_dev = midi_device_getdevice(c);
if (!midi_dev || (midi_dev->flags & DEVICE_MCA) == (models[temp_model].flags & MODEL_MCA))
{
if (c == 0)
{
SendMessage(h, CB_ADDSTRING, 0, (LPARAM) win_language_get_string_from_id(IDS_2152));
}
else
{
mbstowcs(lptsTemp, s, strlen(s) + 1);
SendMessage(h, CB_ADDSTRING, 0, (LPARAM) lptsTemp);
}
settings_list_to_midi[d] = c;
d++;
}
}
c++;
}
SendMessage(h, CB_SETCURSEL, settings_sound_to_list[temp_midi_device], 0);
h = GetDlgItem(hdlg, IDC_CONFIGURE_MIDI);
if (midi_device_has_config(temp_midi_device))
{
EnableWindow(h, TRUE);
}
else
{
EnableWindow(h, FALSE);
}
EnableWindow(h, mpu401_present() ? TRUE : FALSE);
@@ -1251,13 +1328,6 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_CONFIGURE_SND:
h = GetDlgItem(hdlg, IDC_COMBO_SOUND);
temp_sound_card = settings_list_to_sound[SendMessage(h, CB_GETCURSEL, 0, 0)];
deviceconfig_open(hdlg, (void *)sound_card_getdevice(temp_sound_card));
break;
case IDC_COMBO_SOUND:
h = GetDlgItem(hdlg, IDC_COMBO_SOUND);
temp_sound_card = settings_list_to_sound[SendMessage(h, CB_GETCURSEL, 0, 0)];
@@ -1276,6 +1346,38 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
EnableWindow(h, mpu401_present() ? TRUE : FALSE);
break;
case IDC_CONFIGURE_SND:
h = GetDlgItem(hdlg, IDC_COMBO_SOUND);
temp_sound_card = settings_list_to_sound[SendMessage(h, CB_GETCURSEL, 0, 0)];
deviceconfig_open(hdlg, (void *)sound_card_getdevice(temp_sound_card));
break;
case IDC_COMBO_MIDI:
h = GetDlgItem(hdlg, IDC_COMBO_MIDI);
temp_midi_device = settings_list_to_midi[SendMessage(h, CB_GETCURSEL, 0, 0)];
h = GetDlgItem(hdlg, IDC_CONFIGURE_MIDI);
if (midi_device_has_config(temp_midi_device))
{
EnableWindow(h, TRUE);
}
else
{
EnableWindow(h, FALSE);
}
h = GetDlgItem(hdlg, IDC_COMBO_MIDI);
EnableWindow(h, mpu401_present() ? TRUE : FALSE);
break;
case IDC_CONFIGURE_MIDI:
h = GetDlgItem(hdlg, IDC_COMBO_MIDI);
temp_midi_device = settings_list_to_midi[SendMessage(h, CB_GETCURSEL, 0, 0)];
deviceconfig_open(hdlg, (void *)midi_device_getdevice(temp_midi_device));
break;
case IDC_CHECK_MPU401:
h = GetDlgItem(hdlg, IDC_CHECK_MPU401);
temp_mpu401 = SendMessage(h, BM_GETCHECK, 0, 0);
@@ -1298,7 +1400,7 @@ static BOOL CALLBACK win_settings_sound_proc(HWND hdlg, UINT message, WPARAM wPa
temp_sound_card = settings_list_to_sound[SendMessage(h, CB_GETCURSEL, 0, 0)];
h = GetDlgItem(hdlg, IDC_COMBO_MIDI);
temp_midi_id = SendMessage(h, CB_GETCURSEL, 0, 0);
temp_midi_device = settings_list_to_midi[SendMessage(h, CB_GETCURSEL, 0, 0)];
h = GetDlgItem(hdlg, IDC_CHECK_MPU401);
temp_mpu401 = SendMessage(h, BM_GETCHECK, 0, 0);
@@ -4185,12 +4287,15 @@ static BOOL CALLBACK win_settings_main_proc(HWND hdlg, UINT message, WPARAM wPar
win_settings_main_image_list_init(h);
win_settings_main_insert_categories(h);
ListView_SetItemState(h, 0, LVIS_FOCUSED | LVIS_SELECTED, 0x000F);
/* Leave this commented out until we do localization. */
#if 0
h = GetDlgItem(hdlg, IDC_COMBO_LANG); /* This is currently disabled, I am going to add localization options in the future. */
EnableWindow(h, FALSE);
ShowWindow(h, SW_HIDE);
h = GetDlgItem(hdlg, IDS_LANG_ENUS); /*was:2047 !*/
EnableWindow(h, FALSE);
ShowWindow(h, SW_HIDE);
#endif
return TRUE;
case WM_NOTIFY:
if ((((LPNMHDR)lParam)->code == LVN_ITEMCHANGED) && (((LPNMHDR)lParam)->idFrom == IDC_SETTINGSCATLIST))

View File

@@ -29,6 +29,7 @@
#include "scsi.h"
#include "win/plat_joystick.h"
#include "win/plat_midi.h"
#include "sound/midi.h"
#include "sound/snd_dbopl.h"
#include "sound/snd_mpu401.h"
#include "sound/snd_opl.h"
@@ -766,9 +767,6 @@ void config_save(wchar_t *fn)
}
static wchar_t *read_nvr_path;
/* General */
static void loadconfig_general(void)
{
@@ -853,26 +851,18 @@ static void loadconfig_machine(void)
cpu_waitstates = config_get_int(cat, "cpu_waitstates", 0);
mem_size = config_get_int(cat, "mem_size", 4096);
if (mem_size < ((models[model].flags & MODEL_AT) ? models[model].min_ram*1024 : models[model].min_ram))
mem_size = ((models[model].flags & MODEL_AT) ? models[model].min_ram*1024 : models[model].min_ram);
if (mem_size < (((models[model].flags & MODEL_AT) && (models[model].ram_granularity < 128)) ? models[model].min_ram*1024 : models[model].min_ram))
mem_size = (((models[model].flags & MODEL_AT) && (models[model].ram_granularity < 128)) ? models[model].min_ram*1024 : models[model].min_ram);
if (mem_size > 262144)
{
mem_size = 262144;
}
if (read_nvr_path != NULL)
{
free(read_nvr_path);
read_nvr_path = NULL;
}
memset(nvr_path, 0x00, sizeof(nvr_path));
wp = config_get_wstring(cat, "nvr_path", L"");
if (wp != NULL) {
if (wcslen(wp) && (wcslen(wp) <= 992))
{
read_nvr_path = (wchar_t *) malloc((wcslen(wp) << 1) + 2);
wcscpy(read_nvr_path, wp);
wcscpy(nvr_path, wp);
}
else
@@ -977,7 +967,12 @@ static void loadconfig_sound(void)
else
sound_card_current = 0;
midi_id = config_get_int(cat, "midi_host_device", 0);
p = (char *)config_get_string(cat, "midi_device", NULL);
if (p != NULL)
midi_device_current = midi_device_get_from_internal_name(p);
else
midi_device_current = 0;
mpu401_standalone_enable = !!config_get_int(cat, "mpu401_standalone", 0);
SSI2001 = !!config_get_int(cat, "ssi2001", 0);
@@ -1867,14 +1862,7 @@ static void saveconfig_machine(void)
config_set_int(cat, "mem_size", mem_size);
}
if (read_nvr_path == NULL)
{
config_delete_var(cat, "nvr_path");
}
else
{
config_set_wstring(cat, "nvr_path", nvr_path);
}
config_set_wstring(cat, "nvr_path", nvr_path);
config_set_int(cat, "cpu_use_dynarec", cpu_use_dynarec);
@@ -2021,13 +2009,14 @@ static void saveconfig_sound(void)
config_set_string(cat, "sndcard", sound_card_get_internal_name(sound_card_current));
}
if (midi_id == 0)
if (!strcmp(midi_device_get_internal_name(midi_device_current), "none"))
{
config_delete_var(cat, "midi_host_device");
config_delete_var(cat, "midi_device");
}
else
{
config_set_int(cat, "midi_host_device", midi_id);
config_set_string(cat, "midi_device", midi_device_get_internal_name(midi_device_current));
}
if (mpu401_standalone_enable == 0)

View File

@@ -16,6 +16,7 @@ extern int config_get_hex20(char *head, char *name, int def);
extern int config_get_mac(char *head, char *name, int def);
extern char *config_get_string(char *head, char *name, char *def);
extern wchar_t *config_get_wstring(char *head, char *name, wchar_t *def);
extern void config_delete_var(char *head, char *name);
extern void config_set_int(char *head, char *name, int val);
extern void config_set_hex16(char *head, char *name, int val);
extern void config_set_hex20(char *head, char *name, int val);

View File

@@ -20,13 +20,14 @@
# define EMU_DEVICE_H
#define CONFIG_STRING 0
#define CONFIG_INT 1
#define CONFIG_BINARY 2
#define CONFIG_SELECTION 3
#define CONFIG_HEX16 4
#define CONFIG_HEX20 5
#define CONFIG_MAC 6
#define CONFIG_STRING 0
#define CONFIG_INT 1
#define CONFIG_BINARY 2
#define CONFIG_SELECTION 3
#define CONFIG_MIDI 4
#define CONFIG_HEX16 5
#define CONFIG_HEX20 6
#define CONFIG_MAC 7
enum

View File

@@ -764,6 +764,8 @@ extern uint8_t readdacfifo(void);
extern void refreshread(void);
extern int rep386(int fv);
extern void resetmcr(void);
extern void resetpchard_close(void);
extern void resetpchard_init(void);
extern void resetpchard(void);
extern void resetreadlookup(void);
extern void resetx86(void);

Some files were not shown because too many files have changed in this diff Show More