9120e874 Andy McCall 25 days ago
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include " imgui.h"
17
#include " debug_ui_panels.h"
18
#include " debug_ui_bridge.h"
19
#include " debug_ui_widgets.h"
20
#include " debug_ui_scope.h"
21
22
#include < math.h>
23
#include < stdint.h>
24
#include < stdio.h>
25
#include < string.h>
26
27
namespace {
28
29
constexpr double YM_CLOCK_HZ = 3579545.0 ;
30
constexpr double YM_SAMPLE_HZ = YM_CLOCK_HZ / 64.0 ;
31
constexpr int OP_COUNT = 4 ;
32
33
static int s_selected_ch = 0 ;
34
35
struct AlgorithmInfo {
36
uint8_t inputs[ 4 ] ;
37
uint8_t carriers_mask;
38
const char * desc;
39
} ;
40
41
42
43
44
45
46
47
48
49
50
51
52
static const AlgorithmInfo s_alg[ 8 ] = {
53
{ { 0 , 1 , 2 , 3 } , 0x8 , " M1 -> C1 -> M2 -> C2 -> out" } ,
54
{ { 0 , 0 , 5 , 3 } , 0x8 , " (M1 + C1) -> M2 -> C2 -> out" } ,
55
{ { 0 , 0 , 2 , 6 } , 0x8 , " (M1 + (C1 -> M2)) -> C2 -> out" } ,
56
{ { 0 , 1 , 0 , 7 } , 0x8 , " ((M1 -> C1) + M2) -> C2 -> out" } ,
57
{ { 0 , 1 , 0 , 3 } , 0xA , " (M1 -> C1) + (M2 -> C2) -> out" } ,
58
{ { 0 , 1 , 1 , 1 } , 0xE , " M1 modulates C1, M2 and C2; all three out" } ,
59
{ { 0 , 1 , 0 , 0 } , 0xE , " (M1 -> C1) + M2 + C2 -> out" } ,
60
{ { 0 , 0 , 0 , 0 } , 0xF , " M1 + M2 + C1 + C2 -> out" } ,
61
} ;
62
63
64
static const int kOpToSlot [ OP_COUNT] = { 0 , 2 , 1 , 3 } ;
65
66
67
inline uint8_t op_mask_to_slot_mask ( uint8_t op_mask )
68
{
69
uint8_t slots = 0 ;
70
for ( int o = 0 ; o < OP_COUNT; + + o) {
71
if ( ( op_mask & ( 1u << o) ) != 0 ) slots |= ( uint8_t ) ( 1u << kOpToSlot [ o] ) ;
72
}
73
return slots;
74
}
75
76
static const char * s_slot_names[ OP_COUNT] = { " M1" , " M2" , " C1" , " C2" } ;
77
static const char * s_eg_names[ 5 ] = { " -" , " Attack" , " Decay" , " Sustain" , " Release" } ;
78
static const char * s_lfo_waves[ 4 ] = { " saw" , " square" , " triangle" , " noise" } ;
79
80
inline uint8_t ym_reg ( int reg ) { return YM_debug_get_reg ( ( uint8_t ) reg) ; }
81
inline uint8_t ch_reg ( int base , int ch ) { return ym_reg ( base + ch ) ; }
82
inline uint8_t op_reg ( int base , int slot , int ch ) { return ym_reg ( base + slot * 8 + ch ) ; }
83
inline int op_index ( int slot , int ch ) { return slot * 8 + ch; }
84
85
struct NoteInfo {
86
bool valid;
87
char text[ 48 ] ;
88
double hz;
89
} ;
90
91
float clamp01 ( float v )
92
{
93
if ( v < 0.0f ) return 0.0f ;
94
if ( v > 1.0f ) return 1.0f ;
95
return v;
96
}
97
98
uint8_t alg_for_ch ( int ch ) { return ( uint8_t ) ( ch_reg ( 0x20 , ch) & 0x07 ) ; }
99
100
101
102
uint8_t carriers_for_alg ( int alg ) { return op_mask_to_slot_mask ( s_alg[ alg & 7 ] . carriers_mask ) ; }
103
104
float operator_level ( int slot , int ch )
105
{
106
ym_debug_operator op = { } ;
107
if ( ! YM_debug_get_operator ( op_index ( slot, ch ) , & op) ) {
108
return 0.0f ;
109
}
110
return clamp01 ( 1.0f - ( float ) op. eg_attenuation / 1023.0f ) ;
111
}
112
113
float channel_carrier_level ( int ch )
114
{
115
const uint8_t carriers = carriers_for_alg ( alg_for_ch ( ch ) ) ;
116
float level = 0.0f ;
117
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
118
if ( ( carriers & ( 1u << slot) ) != 0 ) {
119
const float l = operator_level ( slot, ch ) ;
120
if ( l > level) level = l;
121
}
122
}
123
return level;
124
}
125
126
bool channel_effectively_silent ( int ch )
127
{
128
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
129
ym_debug_operator op = { } ;
130
if ( YM_debug_get_operator ( op_index ( slot, ch ) , & op) && op. eg_attenuation < 0x3f0 ) {
131
return false ;
132
}
133
}
134
return true ;
135
}
136
137
ImU32 eg_color ( uint8_t state )
138
{
139
switch ( state) {
140
case YM_EG_ATTACK: return IM_COL32 ( 105 , 220 , 125 , 255 ) ;
141
case YM_EG_DECAY: return IM_COL32 ( 235 , 205 , 90 , 255 ) ;
142
case YM_EG_SUSTAIN: return IM_COL32 ( 105 , 170 , 255 , 255 ) ;
143
case YM_EG_RELEASE: return IM_COL32 ( 195 , 130 , 230 , 255 ) ;
144
default : return IM_COL32 ( 120 , 126 , 136 , 255 ) ;
145
}
146
}
147
148
void note_from_kc_kf ( uint8_t kc , uint8_t kf , NoteInfo * out )
149
{
150
static const char * names[ 12 ] = { " C#" , " D" , " D#" , " E" , " F" , " F#" , " G" , " G#" , " A" , " A#" , " B" , " C" } ;
151
const int oct = kc >> 4 ;
152
const int low = kc & 0x0f ;
153
const int semi = ( kc & 3 ) + 3 * ( ( kc >> 2 ) & 3 ) ;
154
out-> valid = ( low != 3 && low != 7 && low != 11 && low != 15 ) ;
155
if ( ! out-> valid ) {
156
out-> hz = 0.0 ;
157
snprintf ( out-> text , sizeof ( out-> text ) , " invalid KC %02X " , kc ) ;
158
return ;
159
}
160
const double fine_semi = ( double ) ( kf >> 2 ) / 64.0 ;
161
const double midi = 12.0 * ( double ) ( oct + 1 ) + 1.0 + ( double ) semi + fine_semi;
162
out-> hz = 440.0 * pow ( 2.0 , ( midi - 69.0 ) / 12.0 ) ;
163
snprintf ( out-> text , sizeof ( out-> text ) , " %s %d %+d c %.2f Hz" , names[ semi] , oct, ( int ) ( fine_semi * 100.0 + 0.5 ) , out-> hz ) ;
164
}
165
166
void draw_key_mask ( uint8_t mask )
167
{
168
169
170
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
171
if ( slot != 0 ) ImGui:: SameLine ( 0.0f , 4.0f ) ;
172
ImGui:: TextColored ( ( mask & ( 1u << slot) ) ? ImVec4 ( 0.45f , 1.0f , 0.55f , 1.0f )
173
: ImVec4 ( 0.35f , 0.36f , 0.38f , 1.0f ) ,
174
" %s " , s_slot_names[ slot] ) ;
175
}
176
}
177
178
void draw_eg_summary ( int ch )
179
{
180
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
181
ym_debug_operator op = { } ;
182
uint8_t state = 0 ;
183
if ( YM_debug_get_operator ( op_index ( slot, ch ) , & op) ) {
184
state = op. eg_state ;
185
}
186
const char c = ( state == YM_EG_ATTACK) ? ' A' : ( state == YM_EG_DECAY) ? ' D' :
187
( state == YM_EG_SUSTAIN) ? ' S' : ( state == YM_EG_RELEASE) ? ' R' : ' -' ;
188
if ( slot != 0 ) ImGui:: SameLine ( 0.0f , 4.0f ) ;
189
ImGui:: TextColored ( ImColor ( eg_color ( state ) ) , " %c " , c) ;
190
}
191
}
192
193
void draw_channels_tab ( )
194
{
195
196
197
198
199
ImGui:: TextDisabled ( " Channels are 0-7, matching the $20+ch / $28+ch register offsets "
200
" and the $08 key-on channel field." ) ;
201
if ( ImGui:: BeginTable ( " ym_channels" , 11 , ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable) ) {
202
ImGui:: TableSetupColumn ( " Ch" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Ch" , " 00" ) ) ;
203
ImGui:: TableSetupColumn ( " KC / note" ) ;
204
ImGui:: TableSetupColumn ( " KF" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " KF" , " $FF" ) ) ;
205
ImGui:: TableSetupColumn ( " ALG" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " ALG" , " 000" ) ) ;
206
ImGui:: TableSetupColumn ( " FB" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " FB" , " 00" ) ) ;
207
ImGui:: TableSetupColumn ( " Pan" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Pan" , " L+R" ) ) ;
208
ImGui:: TableSetupColumn ( " PMS" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " PMS" , " 000" ) ) ;
209
ImGui:: TableSetupColumn ( " AMS" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " AMS" , " 000" ) ) ;
210
ImGui:: TableSetupColumn ( " Key-on" ) ;
211
ImGui:: TableSetupColumn ( " EG" ) ;
212
ImGui:: TableSetupColumn ( " Level" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Level" , " -00.0 dB" ) ) ;
213
ImGui:: TableHeadersRow ( ) ;
214
for ( int ch = 0 ; ch < YM_NUM_CHANNELS; + + ch) {
215
const uint8_t r20 = ch_reg ( 0x20 , ch) ;
216
const uint8_t kc = ch_reg ( 0x28 , ch) & 0x7f ;
217
const uint8_t kf = ch_reg ( 0x30 , ch) & 0xfc ;
218
const uint8_t r38 = ch_reg ( 0x38 , ch) ;
219
NoteInfo note;
220
note_from_kc_kf ( kc, kf, & note ) ;
221
const bool silent = channel_effectively_silent ( ch ) ;
222
223
ImGui:: TableNextRow ( ) ;
224
if ( silent) ImGui:: BeginDisabled ( ) ;
225
ImGui:: TableSetColumnIndex ( 0 ) ;
226
char id[ 16 ] ;
227
snprintf ( id, sizeof ( id ) , " Ch %d " , ch ) ;
228
if ( ImGui:: Selectable ( id, s_selected_ch == ch, ImGuiSelectableFlags_SpanAllColumns ) ) {
229
s_selected_ch = ch;
230
}
231
ImGui:: TableSetColumnIndex ( 1 ) ;
232
ImGui:: Text ( " $%02X %s " , kc, note. text ) ;
233
if ( ! note. valid ) ImGui:: SameLine ( ) , ImGui:: TextColored ( ImVec4 ( 1.0f , 0.45f , 0.35f , 1.0f ) , " !" ) ;
234
ImGui:: TableSetColumnIndex ( 2 ) ; ImGui:: Text ( " %2u " , ( unsigned ) ( kf >> 2 ) ) ;
235
ImGui:: TableSetColumnIndex ( 3 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( r20 & 7 ) ) ;
236
ImGui:: TableSetColumnIndex ( 4 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( r20 >> 3 ) & 7 ) ) ;
237
ImGui:: TableSetColumnIndex ( 5 ) ; ImGui:: Text ( " %c /%c " , ( r20 & 0x40 ) ? ' L' : ' -' , ( r20 & 0x80 ) ? ' R' : ' -' ) ;
238
ImGui:: TableSetColumnIndex ( 6 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( r38 >> 4 ) & 7 ) ) ;
239
ImGui:: TableSetColumnIndex ( 7 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( r38 & 3 ) ) ;
240
ImGui:: TableSetColumnIndex ( 8 ) ; draw_key_mask ( YM_debug_get_keyon ( ch ) ) ;
241
ImGui:: TableSetColumnIndex ( 9 ) ; draw_eg_summary ( ch ) ;
242
ImGui:: TableSetColumnIndex ( 10 ) ;
243
snprintf ( id, sizeof ( id ) , " ##lev%d " , ch ) ;
244
dbgui_level_meter ( id, channel_carrier_level ( ch ) , ImVec2 ( 80.0f , 0.0f ) , IM_COL32 ( 80 , 190 , 110 , 255 ) ) ;
245
if ( silent) ImGui:: EndDisabled ( ) ;
246
}
247
ImGui:: EndTable ( ) ;
248
}
249
}
250
251
void draw_env_shape ( int slot , int ch , ImVec2 size )
252
{
253
ImGui:: PushID ( slot * 16 + ch ) ;
254
ImGui:: InvisibleButton ( " ##env" , size) ;
255
const ImVec2 p0 = ImGui:: GetItemRectMin ( ) ;
256
const ImVec2 p1 = ImGui:: GetItemRectMax ( ) ;
257
ImGui:: PopID ( ) ;
258
ImDrawList * dl = ImGui:: GetWindowDrawList ( ) ;
259
dl-> AddRectFilled ( p0, p1, IM_COL32 ( 16 , 18 , 22 , 255 ) ) ;
260
dl-> AddRect ( p0, p1, IM_COL32 ( 70 , 78 , 90 , 255 ) ) ;
261
262
const uint8_t tl = op_reg ( 0x60 , slot, ch) & 0x7f ;
263
const uint8_t ar = op_reg ( 0x80 , slot, ch) & 0x1f ;
264
const uint8_t d1r = op_reg ( 0xa0 , slot, ch) & 0x1f ;
265
const uint8_t d2r = op_reg ( 0xc0 , slot, ch) & 0x1f ;
266
const uint8_t d1l = ( op_reg ( 0xe0 , slot, ch) >> 4 ) & 0x0f ;
267
const uint8_t rr = op_reg ( 0xe0 , slot, ch) & 0x0f ;
268
269
const float w = p1. x - p0. x ;
270
const float h = p1. y - p0. y ;
271
const float loud = ( float ) tl / 127.0f ;
272
const float sustain = ( float ) d1l / 15.0f ;
273
const float x1 = p0. x + w * ( 0.12f + ( 31 - ar) * 0.0025f ) ;
274
const float x2 = x1 + w * ( 0.14f + ( 31 - d1r) * 0.003f ) ;
275
const float x3 = x2 + w * ( 0.18f + ( 31 - d2r) * 0.002f ) ;
276
const float x4 = p1. x - w * ( 0.08f + ( 15 - rr) * 0.004f ) ;
277
const float y0 = p1. y - 2.0f ;
278
const float y_peak = p0. y + 2.0f + h * loud;
279
const float y_sus = p0. y + 2.0f + h * ( loud + ( 1.0f - loud) * sustain) ;
280
ImVec2 pts[ 5 ] = { ImVec2 ( p0. x + 2.0f , y0 ) , ImVec2 ( x1, y_peak ) , ImVec2 ( x2, y_sus ) , ImVec2 ( x3, y_sus ) , ImVec2 ( x4, y0 ) } ;
281
dl-> AddPolyline ( pts, 5 , IM_COL32 ( 120 , 190 , 255 , 255 ) , false , 1.5f ) ;
282
}
283
284
void draw_operator_row ( int ch , int slot )
285
{
286
const uint8_t r40 = op_reg ( 0x40 , slot, ch) ;
287
const uint8_t r60 = op_reg ( 0x60 , slot, ch) ;
288
const uint8_t r80 = op_reg ( 0x80 , slot, ch) ;
289
const uint8_t ra0 = op_reg ( 0xa0 , slot, ch) ;
290
const uint8_t rc0 = op_reg ( 0xc0 , slot, ch) ;
291
const uint8_t re0 = op_reg ( 0xe0 , slot, ch) ;
292
ym_debug_operator op = { } ;
293
YM_debug_get_operator ( op_index ( slot, ch ) , & op) ;
294
295
ImGui:: TableNextRow ( ) ;
296
ImGui:: TableSetColumnIndex ( 0 ) ; ImGui:: Text ( " Ch %d %s " , ch, s_slot_names[ slot] ) ;
297
ImGui:: TableSetColumnIndex ( 1 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( r40 >> 4 ) & 7 ) ) ;
298
ImGui:: TableSetColumnIndex ( 2 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( r40 & 0x0f ) ) ;
299
ImGui:: TableSetColumnIndex ( 3 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( r60 & 0x7f ) ) ;
300
ImGui:: TableSetColumnIndex ( 4 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( r80 >> 6 ) & 3 ) ) ;
301
ImGui:: TableSetColumnIndex ( 5 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( r80 & 0x1f ) ) ;
302
ImGui:: TableSetColumnIndex ( 6 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ra0 & 0x1f ) ) ;
303
ImGui:: TableSetColumnIndex ( 7 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( rc0 >> 6 ) & 3 ) ) ;
304
ImGui:: TableSetColumnIndex ( 8 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( rc0 & 0x1f ) ) ;
305
ImGui:: TableSetColumnIndex ( 9 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( ( re0 >> 4 ) & 0x0f ) ) ;
306
ImGui:: TableSetColumnIndex ( 10 ) ; ImGui:: Text ( " %u " , ( unsigned ) ( re0 & 0x0f ) ) ;
307
ImGui:: TableSetColumnIndex ( 11 ) ; ImGui:: TextUnformatted ( ( ra0 & 0x80 ) ? " yes" : " no" ) ;
308
ImGui:: TableSetColumnIndex ( 12 ) ; ImGui:: TextColored ( ImColor ( eg_color ( op. eg_state ) ) , " %s " , s_eg_names[ ( op. eg_state <= YM_EG_RELEASE) ? op. eg_state : 0 ] ) ;
309
ImGui:: TableSetColumnIndex ( 13 ) ;
310
char id[ 32 ] ;
311
snprintf ( id, sizeof ( id ) , " ##att%d _%d " , ch, slot ) ;
312
dbgui_level_meter ( id, operator_level ( slot, ch ) , ImVec2 ( 90.0f , 0.0f ) , IM_COL32 ( 90 , 170 , 240 , 255 ) ) ;
313
ImGui:: TableSetColumnIndex ( 14 ) ;
314
draw_env_shape ( slot, ch, ImVec2 ( 110.0f , 28.0f ) ) ;
315
}
316
317
void draw_operators_tab ( )
318
{
319
static bool show_all = false ;
320
ImGui:: Checkbox ( " Show all 32 operators grouped by channel" , & show_all) ;
321
ImGui:: TextDisabled ( " Envelope sketches are qualitative ADSR/TL shapes, not cycle-accurate ymfm EG traces." ) ;
322
const int first_ch = show_all ? 0 : s_selected_ch;
323
const int last_ch = show_all ? YM_NUM_CHANNELS : ( s_selected_ch + 1 ) ;
324
if ( ImGui:: BeginTable ( " ym_ops" , 15 , ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2 ( 0.0f , 0.0f ) ) ) {
325
const char * cols[ 15 ] = { " Slot" , " DT1" , " MUL" , " TL" , " KS" , " AR" , " D1R" , " DT2" , " D2R" , " D1L" , " RR" , " AMS-EN" , " EG state" , " Atten" , " ADSR sketch" } ;
326
for ( int i = 0 ; i < 15 ; + + i) ImGui:: TableSetupColumn ( cols[ i] ) ;
327
ImGui:: TableHeadersRow ( ) ;
328
for ( int ch = first_ch; ch < last_ch; + + ch) {
329
for ( int slot = 0 ; slot < OP_COUNT; + + slot) draw_operator_row ( ch, slot ) ;
330
}
331
ImGui:: EndTable ( ) ;
332
}
333
}
334
335
uint8_t source_mask ( uint8_t selector )
336
{
337
switch ( selector) {
338
case 1 : return 0x1 ;
339
case 2 : return 0x2 ;
340
case 3 : return 0x4 ;
341
case 5 : return 0x3 ;
342
case 6 : return 0x5 ;
343
case 7 : return 0x6 ;
344
default : return 0x0 ;
345
}
346
}
347
348
void draw_arrow ( ImDrawList * dl , ImVec2 a , ImVec2 b , ImU32 color )
349
{
350
dl-> AddLine ( a, b, color, 2.0f ) ;
351
const float ang = atan2f ( b. y - a. y , b. x - a. x ) ;
352
const float len = 8.0f ;
353
const ImVec2 p1 ( b. x - cosf ( ang - 0.45f ) * len, b. y - sinf ( ang - 0.45f ) * len ) ;
354
const ImVec2 p2 ( b. x - cosf ( ang + 0.45f ) * len, b. y - sinf ( ang + 0.45f ) * len ) ;
355
dl-> AddTriangleFilled ( b, p1, p2, color ) ;
356
}
357
358
void draw_algorithm_tab ( )
359
{
360
const int ch = s_selected_ch;
361
const int alg = alg_for_ch ( ch ) ;
362
const uint8_t fb = ( ch_reg ( 0x20 , ch) >> 3 ) & 7 ;
363
ImGui:: Text ( " Channel %d ALG %d : %s " , ch, alg, s_alg[ alg] . desc ) ;
364
ImGui:: TextDisabled ( " Algorithm table is derived from ymfm_fm.ipp output_4op(). Box brightness follows live EG attenuation." ) ;
365
366
const ImVec2 canvas = ImVec2 ( ImGui:: GetContentRegionAvail ( ) . x , 330.0f ) ;
367
ImGui:: InvisibleButton ( " alg_canvas" , canvas) ;
368
const ImVec2 p0 = ImGui:: GetItemRectMin ( ) ;
369
const ImVec2 p1 = ImGui:: GetItemRectMax ( ) ;
370
ImDrawList * dl = ImGui:: GetWindowDrawList ( ) ;
371
dl-> AddRectFilled ( p0, p1, IM_COL32 ( 14 , 16 , 20 , 255 ) ) ;
372
dl-> AddRect ( p0, p1, IM_COL32 ( 70 , 78 , 90 , 255 ) ) ;
373
374
ImVec2 c[ OP_COUNT] = {
375
ImVec2 ( p0. x + canvas. x * 0.16f , p0. y + canvas. y * 0.42f ) ,
376
ImVec2 ( p0. x + canvas. x * 0.38f , p0. y + canvas. y * 0.25f ) ,
377
ImVec2 ( p0. x + canvas. x * 0.38f , p0. y + canvas. y * 0.62f ) ,
378
ImVec2 ( p0. x + canvas. x * 0.64f , p0. y + canvas. y * 0.42f ) ,
379
} ;
380
const ImVec2 box ( 70.0f , 42.0f ) ;
381
const uint8_t carriers = carriers_for_alg ( alg ) ;
382
const ImU32 mod_col = IM_COL32 ( 140 , 170 , 220 , 255 ) ;
383
const ImU32 out_col = IM_COL32 ( 110 , 230 , 130 , 255 ) ;
384
385
386
387
388
for ( int dst_op = 1 ; dst_op < OP_COUNT; + + dst_op) {
389
const uint8_t srcs = source_mask ( s_alg[ alg] . inputs [ dst_op] ) ;
390
const int dst = kOpToSlot [ dst_op] ;
391
for ( int src_op = 0 ; src_op < OP_COUNT; + + src_op) {
392
if ( ( srcs & ( 1u << src_op) ) != 0 ) {
393
const int src = kOpToSlot [ src_op] ;
394
draw_arrow ( dl, ImVec2 ( c[ src] . x + box. x * 0.5f , c[ src] . y ) , ImVec2 ( c[ dst] . x - box. x * 0.5f , c[ dst] . y ) , mod_col ) ;
395
}
396
}
397
}
398
if ( fb != 0 ) {
399
const ImVec2 a ( c[ 0 ] . x - 18.0f , c[ 0 ] . y - box. y * 0.5f ) ;
400
const ImVec2 b ( c[ 0 ] . x + 18.0f , c[ 0 ] . y - box. y * 0.5f ) ;
401
dl-> AddBezierCubic ( a, ImVec2 ( a. x - 45.0f , a. y - 60.0f ) , ImVec2 ( b. x + 45.0f , b. y - 60.0f ) , b, IM_COL32 ( 235 , 170 , 80 , 255 ) , 2.0f ) ;
402
dl-> AddText ( ImVec2 ( c[ 0 ] . x - 18.0f , a. y - 58.0f ) , IM_COL32 ( 235 , 185 , 100 , 255 ) , " FB" ) ;
403
}
404
const ImVec2 out ( p0. x + canvas. x * 0.86f , p0. y + canvas. y * 0.42f ) ;
405
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
406
if ( ( carriers & ( 1u << slot) ) != 0 ) {
407
draw_arrow ( dl, ImVec2 ( c[ slot] . x + box. x * 0.5f , c[ slot] . y ) , out, out_col ) ;
408
}
409
}
410
dl-> AddText ( ImVec2 ( out. x + 8.0f , out. y - 8.0f ) , out_col, " OUT" ) ;
411
412
for ( int slot = 0 ; slot < OP_COUNT; + + slot) {
413
const float lvl = operator_level ( slot, ch ) ;
414
const int base = ( int ) ( 55.0f + lvl * 150.0f ) ;
415
const bool carrier = ( carriers & ( 1u << slot) ) != 0 ;
416
const ImU32 fill = carrier ? IM_COL32 ( 35 , base, 65 , 255 ) : IM_COL32 ( 35 , 55 , base, 255 ) ;
417
const ImVec2 b0 ( c[ slot] . x - box. x * 0.5f , c[ slot] . y - box. y * 0.5f ) ;
418
const ImVec2 b1 ( c[ slot] . x + box. x * 0.5f , c[ slot] . y + box. y * 0.5f ) ;
419
dl-> AddRectFilled ( b0, b1, fill, 5.0f ) ;
420
dl-> AddRect ( b0, b1, carrier ? out_col : mod_col, 5.0f , 0 , 2.0f ) ;
421
char label[ 32 ] ;
422
snprintf ( label, sizeof ( label ) , " %s \n %.0f %% " , s_slot_names[ slot] , lvl * 100.0f ) ;
423
dl-> AddText ( ImVec2 ( b0. x + 8.0f , b0. y + 5.0f ) , IM_COL32 ( 235 , 240 , 245 , 255 ) , label) ;
424
}
425
}
426
427
void draw_global_tab ( )
428
{
429
const uint8_t r0f = ym_reg ( 0x0f ) ;
430
const uint8_t r14 = ym_reg ( 0x14 ) ;
431
const uint8_t r18 = ym_reg ( 0x18 ) ;
432
const uint8_t r19 = ym_reg ( 0x19 ) ;
433
const uint8_t r1a = ym_reg ( 0x1a ) ;
434
const uint8_t r1b = ym_reg ( 0x1b ) ;
435
const uint16_t ta = ( uint16_t ) ( ( ym_reg ( 0x10 ) << 2 ) | ( ym_reg ( 0x11 ) & 3 ) ) ;
436
const uint8_t tb = ym_reg ( 0x12 ) ;
437
const double ta_ms = ( 1024.0 - ( double ) ta) / YM_SAMPLE_HZ * 1000.0 ;
438
const double tb_ms = ( 256.0 - ( double ) tb) * 16.0 / YM_SAMPLE_HZ * 1000.0 ;
439
const uint8_t status = YM_debug_get_status ( ) ;
440
441
ImGui:: Text ( " Clock: %.0f Hz ymfm sample rate: %.3f Hz" , YM_CLOCK_HZ, YM_SAMPLE_HZ) ;
442
ImGui:: Separator ( ) ;
443
ImGui:: Text ( " LFO: rate $%02X , waveform %s , AM depth %u , PM depth %u " , r18, s_lfo_waves[ r1b & 3 ] , ( unsigned ) ( r19 & 0x7f ) , ( unsigned ) ( r1a & 0x7f ) ) ;
444
ImGui:: TextDisabled ( " $19 writes select AM (bit7=0) or PM (bit7=1); PM is shown from the shadowed internal $1A." ) ;
445
ImGui:: Text ( " Noise: %s , frequency %u " , ( r0f & 0x80 ) ? " enabled" : " disabled" , ( unsigned ) ( r0f & 0x1f ) ) ;
446
ImGui:: Text ( " CT pins: CT1=%u CT2=%u " , ( unsigned ) ( ( r1b >> 6 ) & 1 ) , ( unsigned ) ( ( r1b >> 7 ) & 1 ) ) ;
447
ImGui:: Separator ( ) ;
448
ImGui:: Text ( " Timer A: %u approx %.3f ms" , ( unsigned ) ta, ta_ms) ;
449
ImGui:: Text ( " Timer B: %u approx %.3f ms" , ( unsigned ) tb, tb_ms) ;
450
ImGui:: Text ( " Mode $14: CSM=%u reset TB/TA=%u /%u enable TB/TA=%u /%u load TB/TA=%u /%u " ,
451
( unsigned ) ( ( r14 >> 7 ) & 1 ) , ( unsigned ) ( ( r14 >> 5 ) & 1 ) , ( unsigned ) ( ( r14 >> 4 ) & 1 ) ,
452
( unsigned ) ( ( r14 >> 3 ) & 1 ) , ( unsigned ) ( ( r14 >> 2 ) & 1 ) , ( unsigned ) ( ( r14 >> 1 ) & 1 ) , ( unsigned ) ( r14 & 1 ) ) ;
453
ImGui:: Separator ( ) ;
454
ImGui:: Text ( " Status $%02X : Timer A ovf=%u Timer B ovf=%u busy bit=%u " , status, ( unsigned ) ( status & 1 ) , ( unsigned ) ( ( status >> 1 ) & 1 ) , ( unsigned ) ( ( status >> 7 ) & 1 ) ) ;
455
ImGui:: Text ( " YM_debug_is_busy(): %s " , YM_debug_is_busy ( ) ? " true" : " false" ) ;
456
}
457
458
const char * system_name ( int reg )
459
{
460
switch ( reg) {
461
case 0x01 : return " TEST / LFO reset" ;
462
case 0x08 : return " Key-on" ;
463
case 0x0f : return " Noise" ;
464
case 0x10 : return " Timer A high" ;
465
case 0x11 : return " Timer A low" ;
466
case 0x12 : return " Timer B" ;
467
case 0x14 : return " Timer control" ;
468
case 0x18 : return " LFO frequency" ;
469
case 0x19 : return " AM/PM depth write" ;
470
case 0x1a : return " PM depth shadow" ;
471
case 0x1b : return " CT / LFO wave" ;
472
default : return " System" ;
473
}
474
}
475
476
void decode_reg ( int reg , uint8_t v , char * out , size_t outsz )
477
{
478
if ( reg < 0x20 ) {
479
switch ( reg) {
480
case 0x01 : snprintf ( out, outsz, " test=$%02X LFO reset=%u " , v, ( unsigned ) ( ( v >> 1 ) & 1 ) ) ; break ;
481
case 0x08 : snprintf ( out, outsz, " ch=%u raw op bits b3-b6=M1,C1,M2,C2" , ( unsigned ) ( v & 7 ) ) ; break ;
482
case 0x0f : snprintf ( out, outsz, " noise %s freq=%u " , ( v & 0x80 ) ? " on" : " off" , ( unsigned ) ( v & 0x1f ) ) ; break ;
483
case 0x14 : snprintf ( out, outsz, " CSM=%u en TB/TA=%u /%u load TB/TA=%u /%u " , ( unsigned ) ( v >> 7 ) , ( unsigned ) ( ( v >> 3 ) & 1 ) , ( unsigned ) ( ( v >> 2 ) & 1 ) , ( unsigned ) ( ( v >> 1 ) & 1 ) , ( unsigned ) ( v & 1 ) ) ; break ;
484
case 0x18 : snprintf ( out, outsz, " LFO freq=%u " , ( unsigned ) v ) ; break ;
485
case 0x19 : snprintf ( out, outsz, " %s depth write=%u " , ( v & 0x80 ) ? " PM" : " AM" , ( unsigned ) ( v & 0x7f ) ) ; break ;
486
case 0x1b : snprintf ( out, outsz, " CT=%u wave=%s " , ( unsigned ) ( ( v >> 6 ) & 3 ) , s_lfo_waves[ v & 3 ] ) ; break ;
487
default : snprintf ( out, outsz, " %s " , system_name ( reg ) ) ; break ;
488
}
489
} else if ( reg < 0x40 ) {
490
const int ch = reg & 7 ;
491
const int base = reg & 0xf8 ;
492
if ( base == 0x20 ) snprintf ( out, outsz, " Ch%d pan L/R=%u /%u FB=%u ALG=%u " , ch, ( unsigned ) ( ( v >> 6 ) & 1 ) , ( unsigned ) ( ( v >> 7 ) & 1 ) , ( unsigned ) ( ( v >> 3 ) & 7 ) , ( unsigned ) ( v & 7 ) ) ;
493
else if ( base == 0x28 ) snprintf ( out, outsz, " Ch%d KC=%u " , ch, ( unsigned ) ( v & 0x7f ) ) ;
494
else if ( base == 0x30 ) snprintf ( out, outsz, " Ch%d KF=%u " , ch, ( unsigned ) ( v >> 2 ) ) ;
495
else if ( base == 0x38 ) snprintf ( out, outsz, " Ch%d PMS=%u AMS=%u " , ch, ( unsigned ) ( ( v >> 4 ) & 7 ) , ( unsigned ) ( v & 3 ) ) ;
496
else snprintf ( out, outsz, " Ch%d unused/shadow" , ch ) ;
497
} else {
498
const int off = reg - 0x40 ;
499
const int group = reg & 0xe0 ;
500
const int slot = ( off >> 3 ) & 3 ;
501
const int ch = off & 7 ;
502
if ( group == 0x40 ) snprintf ( out, outsz, " %s Ch%d DT1=%u MUL=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( ( v >> 4 ) & 7 ) , ( unsigned ) ( v & 0x0f ) ) ;
503
else if ( group == 0x60 ) snprintf ( out, outsz, " %s Ch%d TL=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( v & 0x7f ) ) ;
504
else if ( group == 0x80 ) snprintf ( out, outsz, " %s Ch%d KS=%u AR=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( ( v >> 6 ) & 3 ) , ( unsigned ) ( v & 0x1f ) ) ;
505
else if ( group == 0xa0 ) snprintf ( out, outsz, " %s Ch%d AMS-EN=%u D1R=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( ( v >> 7 ) & 1 ) , ( unsigned ) ( v & 0x1f ) ) ;
506
else if ( group == 0xc0 ) snprintf ( out, outsz, " %s Ch%d DT2=%u D2R=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( ( v >> 6 ) & 3 ) , ( unsigned ) ( v & 0x1f ) ) ;
507
else snprintf ( out, outsz, " %s Ch%d D1L=%u RR=%u " , s_slot_names[ slot] , ch, ( unsigned ) ( ( v >> 4 ) & 0x0f ) , ( unsigned ) ( v & 0x0f ) ) ;
508
}
509
}
510
511
void draw_register_range ( const char * label , int first , int last )
512
{
513
ImGui:: TextUnformatted ( label ) ;
514
if ( ImGui:: BeginTable ( label, 5 , ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable ) ) {
515
ImGui:: TableSetupColumn ( " Reg" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Reg" , " $FF" ) ) ;
516
ImGui:: TableSetupColumn ( " Layout" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Layout" , " 1111 1111" ) ) ;
517
ImGui:: TableSetupColumn ( " Name" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Name" , " AMS-EN / D1R" ) ) ;
518
ImGui:: TableSetupColumn ( " Value" , ImGuiTableColumnFlags_WidthFixed, dbgui_col_width ( " Value" , " $FF (255)" ) ) ;
519
ImGui:: TableSetupColumn ( " Decode" ) ;
520
ImGui:: TableHeadersRow ( ) ;
521
for ( int reg = first; reg <= last; + + reg) {
522
const uint8_t v = ym_reg ( reg ) ;
523
char bin[ 16 ] ;
524
char layout[ 32 ] ;
525
char name[ 64 ] ;
526
char dec[ 160 ] ;
527
dbgui_format_binary ( bin, sizeof ( bin ) , v, 8 ) ;
528
if ( reg < 0x20 ) snprintf ( layout, sizeof ( layout ) , " system" ) ;
529
else if ( reg < 0x40 ) snprintf ( layout, sizeof ( layout ) , " ch %d " , ( reg & 7 ) ) ;
530
else snprintf ( layout, sizeof ( layout ) , " %s ch %d " , s_slot_names[ ( ( reg - 0x40 ) >> 3 ) & 3 ] , ( reg & 7 ) ) ;
531
if ( reg < 0x20 ) snprintf ( name, sizeof ( name ) , " %s " , system_name ( reg ) ) ;
532
else if ( reg < 0x40 ) snprintf ( name, sizeof ( name ) , " %s " , ( ( reg & 0xf8 ) == 0x20 ) ? " Pan/FB/ALG" : ( ( reg & 0xf8 ) == 0x28 ) ? " KC" : ( ( reg & 0xf8 ) == 0x30 ) ? " KF" : ( ( reg & 0xf8 ) == 0x38 ) ? " PMS/AMS" : " unused" ) ;
533
else snprintf ( name, sizeof ( name ) , " %s " , ( ( reg & 0xe0 ) == 0x40 ) ? " DT1/MUL" : ( ( reg & 0xe0 ) == 0x60 ) ? " TL" : ( ( reg & 0xe0 ) == 0x80 ) ? " KS/AR" : ( ( reg & 0xe0 ) == 0xa0 ) ? " AMS-EN/D1R" : ( ( reg & 0xe0 ) == 0xc0 ) ? " DT2/D2R" : " D1L/RR" ) ;
534
decode_reg ( reg, v, dec, sizeof ( dec ) ) ;
535
ImGui:: TableNextRow ( ) ;
536
ImGui:: TableSetColumnIndex ( 0 ) ; ImGui:: Text ( " $%02X " , reg) ; dbgui_hover_value_tooltip ( dec, v, 1 ) ;
537
ImGui:: TableSetColumnIndex ( 1 ) ; ImGui:: TextUnformatted ( layout ) ;
538
ImGui:: TableSetColumnIndex ( 2 ) ; ImGui:: TextUnformatted ( name ) ;
539
ImGui:: TableSetColumnIndex ( 3 ) ; ImGui:: Text ( " $%02X %s " , v, bin) ; dbgui_hover_value_tooltip ( dec, v, 1 ) ;
540
ImGui:: TableSetColumnIndex ( 4 ) ; ImGui:: TextUnformatted ( dec ) ;
541
}
542
ImGui:: EndTable ( ) ;
543
}
544
}
545
546
void draw_registers_tab ( )
547
{
548
ImGui:: TextDisabled ( " Shadowed registers show only writes the chip accepted; YM_write_reg drops writes while the chip is busy." ) ;
549
ImGui:: TextDisabled ( " Per-operator address layout is base + (slot << 3) + channel; slots are M1, M2, C1, C2. Raw $08 key-on bits are M1,C1,M2,C2, but live key masks are normalized." ) ;
550
if ( ImGui:: CollapsingHeader ( " $00-$1F system" , ImGuiTreeNodeFlags_DefaultOpen) ) draw_register_range ( " system_regs" , 0x00 , 0x1f ) ;
551
if ( ImGui:: CollapsingHeader ( " $20-$3F per channel" , ImGuiTreeNodeFlags_DefaultOpen) ) draw_register_range ( " channel_regs" , 0x20 , 0x3f ) ;
552
if ( ImGui:: CollapsingHeader ( " $40-$FF per operator" , ImGuiTreeNodeFlags_DefaultOpen) ) draw_register_range ( " operator_regs" , 0x40 , 0xff ) ;
553
}
554
555
void draw_scope_tab ( )
556
{
557
static DbgScopeControls ctl;
558
static int16_t samples[ YM_SCOPE_STREAMS] [ YM_SCOPE_SAMPLES] ;
559
static unsigned counts[ YM_SCOPE_STREAMS] = { } ;
560
static bool captured = false ;
561
562
dbgui_scope_controls ( & ctl, YM_SCOPE_SAMPLES) ;
563
const bool held = dbgui_audio_hold ( ) ;
564
bool predicted = false ;
565
566
if ( ! ctl. freeze && ! held) {
567
captured = false ;
568
for ( int stream = 0 ; stream < YM_SCOPE_STREAMS; + + stream) {
569
counts[ stream] = YM_debug_scope_read ( stream, samples[ stream] , YM_SCOPE_SAMPLES ) ;
570
if ( counts[ stream] > 0 ) captured = true ;
571
}
572
} else if ( held && ! captured && ! ctl. freeze ) {
573
574
575
const unsigned n = YM_debug_scope_predict ( samples, YM_SCOPE_SAMPLES ) ;
576
for ( int stream = 0 ; stream < YM_SCOPE_STREAMS; + + stream) {
577
counts[ stream] = n;
578
}
579
predicted = true ;
580
}
581
582
if ( held) {
583
if ( predicted) {
584
dbgui_audio_predicted_note ( " the YM2151's current envelope and phase state" ) ;
585
} else {
586
dbgui_audio_hold_note ( ) ;
587
}
588
}
589
ImGui:: TextDisabled ( " Per-channel capture is self-armed only while this Scope tab reads it; hidden tabs let it disarm." ) ;
590
591
if ( ImGui:: BeginTable ( " ym_scope_grid" , 2 , ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg) ) {
592
for ( int ch = 0 ; ch < YM_NUM_CHANNELS; + + ch) {
593
if ( ( ch % 2 ) == 0 ) ImGui:: TableNextRow ( ) ;
594
ImGui:: TableSetColumnIndex ( ch % 2 ) ;
595
NoteInfo note;
596
note_from_kc_kf ( ch_reg ( 0x28 , ch) & 0x7f , ch_reg ( 0x30 , ch) & 0xfc , & note) ;
597
char overlay[ 96 ] ;
598
snprintf ( overlay, sizeof ( overlay ) , " Ch %d ALG %u %s " , ch, ( unsigned ) alg_for_ch ( ch ) , note. text ) ;
599
char id[ 32 ] ;
600
snprintf ( id, sizeof ( id ) , " mini%d " , ch ) ;
601
dbgui_scope ( id, samples[ ch] , ( int ) counts[ ch] , ImVec2 ( 0.0f , 58.0f ) , IM_COL32 ( 90 , 190 , 245 , 255 ) , ctl. window , ctl. gain , ctl. trigger , overlay ) ;
602
if ( ImGui:: IsItemClicked ( ) ) s_selected_ch = ch;
603
}
604
ImGui:: EndTable ( ) ;
605
}
606
ImGui:: Separator ( ) ;
607
char overlay[ 96 ] ;
608
NoteInfo note;
609
note_from_kc_kf ( ch_reg ( 0x28 , s_selected_ch) & 0x7f , ch_reg ( 0x30 , s_selected_ch) & 0xfc , & note) ;
610
snprintf ( overlay, sizeof ( overlay ) , " Selected Ch %d ALG %u %s " , s_selected_ch, ( unsigned ) alg_for_ch ( s_selected_ch ) , note. text ) ;
611
dbgui_scope ( " selected" , samples[ s_selected_ch] , ( int ) counts[ s_selected_ch] , ImVec2 ( 0.0f , 120.0f ) , IM_COL32 ( 105 , 225 , 140 , 255 ) , ctl. window , ctl. gain , ctl. trigger , overlay) ;
612
ImGui:: TextUnformatted ( " Summed YM output" ) ;
613
dbgui_scope ( " mix_l" , samples[ YM_SCOPE_MIX_L] , ( int ) counts[ YM_SCOPE_MIX_L] , ImVec2 ( 0.0f , 84.0f ) , IM_COL32 ( 245 , 185 , 90 , 255 ) , ctl. window , ctl. gain , ctl. trigger , " Mix L" ) ;
614
dbgui_scope ( " mix_r" , samples[ YM_SCOPE_MIX_R] , ( int ) counts[ YM_SCOPE_MIX_R] , ImVec2 ( 0.0f , 84.0f ) , IM_COL32 ( 245 , 120 , 160 , 255 ) , ctl. window , ctl. gain , ctl. trigger , " Mix R" ) ;
615
}
616
617
void fm_panel_render ( bool * p_open )
618
{
619
if ( ImGui:: Begin ( " YM2151" , p_open) ) {
620
dbgui_window_zoom ( " ym2151" ) ;
621
if ( s_selected_ch < 0 || s_selected_ch >= YM_NUM_CHANNELS) s_selected_ch = 0 ;
622
if ( ImGui:: BeginTabBar ( " ym2151_tabs" , ImGuiTabBarFlags_DrawSelectedOverline) ) {
623
if ( ImGui:: BeginTabItem ( " Channels" ) ) {
624
draw_channels_tab ( ) ;
625
ImGui:: EndTabItem ( ) ;
626
}
627
if ( ImGui:: BeginTabItem ( " Operators" ) ) {
628
draw_operators_tab ( ) ;
629
ImGui:: EndTabItem ( ) ;
630
}
631
if ( ImGui:: BeginTabItem ( " Algorithm" ) ) {
632
draw_algorithm_tab ( ) ;
633
ImGui:: EndTabItem ( ) ;
634
}
635
if ( ImGui:: BeginTabItem ( " Global" ) ) {
636
draw_global_tab ( ) ;
637
ImGui:: EndTabItem ( ) ;
638
}
639
if ( ImGui:: BeginTabItem ( " Registers" ) ) {
640
draw_registers_tab ( ) ;
641
ImGui:: EndTabItem ( ) ;
642
}
643
if ( ImGui:: BeginTabItem ( " Scope" ) ) {
644
draw_scope_tab ( ) ;
645
ImGui:: EndTabItem ( ) ;
646
}
647
ImGui:: EndTabBar ( ) ;
648
}
649
}
650
dbgui_window_end ( ) ;
651
}
652
653
}
654
655
static DebugPanelRegistration s_reg ( " YM2151" , fm_panel_render , true ) ;