Archived
1
0
This repository has been archived on 2024-10-17. You can view files and clone it, but cannot push or open issues or pull requests.
winamp/Src/Plugins/Input/in_midi/seq.cpp

853 lines
16 KiB
C++
Raw Normal View History

2024-09-24 12:54:57 +00:00
#include "main.h"
#include "seq.h"
#include <commctrl.h>
#include <math.h>
#include "resource.h"
#ifdef SEQ_HAVE_PANEL
cfg_int cfg_seq_showpanel("seq_showpanel",0);
enum
{
ID_BASE = 0x6543,
MUTE_ID = ID_BASE,
VOL_ID = MUTE_ID+16,
INS_ID_P = VOL_ID+16,
INS_ID_B1 = INS_ID_P+16,
INS_ID_B2 = INS_ID_B1+16,
SPIN_ID = INS_ID_B2
};
static cfg_int cfg_ctrl_min("ctrl_min",0);
static float g_tempo=1;
static BOOL g_novol,g_noins;
static char sysex1[256],sysex2[256];
extern BYTE d_GMReset[6];
extern BYTE d_XGReset[9];
extern BYTE d_GSReset[11];
#endif
#define SEND_MSG(X) seq_shortmsg(preprocess(X))
#define _sysex(A,B) seq_sysex(A,B)
#define rsysex(A) seq_sysex(A,sizeof(A))
#ifdef SEQ_HAVE_PANEL
void seq_base::set_mute(UINT ch,BOOL st)
{
if (st)
{
mute_mask|=1<<ch;
seq_shortmsg(0x07B0|ch);
}
else
{
mute_mask&=~(1<<ch);
SEND_MSG(((DWORD)ctrl_tab[ch][7]<<16)|0x07B0|ch);
}
}
#endif
//debug hack
#if 0
#define timeGetTime timehack
static DWORD timehack()
{
static DWORD t;
return t++;
}
#endif
DWORD seq_base::get_time()
{
#ifndef SEQ_HAVE_PANEL
return timeGetTime()<<3;
#else
if (!hCtrl) return timeGetTime()<<3;//*8;
EnterCriticalSection(&tm_sec);
DWORD cur_t=timeGetTime();
if (!last_time_ms) last_time_ms=cur_t;
int d=cur_t-last_time_ms;
if (d<0) d=0;
last_time_ret+=(double)(d*8.0)*tempo;
last_time_ms=cur_t;
DWORD r=(DWORD)last_time_ret;
LeaveCriticalSection(&tm_sec);
return r;
#endif
}
#ifdef SEQ_HAVE_PANEL
BOOL CALLBACK seq_base::CtrlProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp)
{
seq_base* s;
if (msg==WM_INITDIALOG)
{
SetWindowLongPtr(wnd,DWLP_USER,lp);
s=(seq_base*)lp;
if (s) s->hCtrl=wnd;
}
else
{
#if defined(_WIN64)
s = (seq_base*)GetWindowLong(wnd, DWLP_USER);
#else
s = (seq_base*)GetWindowLong(wnd, DWL_USER);
#endif
}
if (s)
{
s->do_msg(msg,wp,lp);
}
return 0;
}
static float ui2tempo(int x)
{
return (float)pow(4.0,0.02*(float)(x-50));
}
static int tempo2ui(float x)
{
return 50+(int) ((50.0 / log(4.0)) * log(x) );
}
static void do_ttext(HWND w,float t)
{
char tx[32] = {0};
_itoa((UINT)(t*100.0),tx,10);
char* p=tx;
while(p && *p) p++;
*(p++)='%';
*p=0;
SetDlgItemTextA(w,IDC_TDISP,tx);
}
BYTE* read_sysex_edit(HWND w,UINT *siz);
void CreateControl(DWORD ex,HWND hCtrl,const char * cls,const char * name,DWORD style,UINT x,UINT y,UINT dx,UINT dy,HINSTANCE hDll,UINT id)
{
RECT r={(LONG)x,(LONG)y,(LONG)(x+dx),(LONG)(y+dy)};
MapDialogRect(hCtrl,&r);
HWND w = CreateWindowExA( ex, cls, name, WS_CHILD | WS_VISIBLE | style, r.left, r.top, r.right - r.left, r.bottom - r.top, hCtrl, 0, hDll, 0 ); // Must stay in ANSI
if (w)
{
if (id) SetWindowLong(w,GWL_ID,id);
SendMessage(w,WM_SETFONT,SendMessage(hCtrl,WM_GETFONT,0,0),MAKELONG(0,0));
}
}
static cfg_int cfg_ctrl_x("ctrl_x",0x80000000),cfg_ctrl_y("ctrl_y",0x80000000);
void seq_base::do_msg(UINT msg,WPARAM wp,LPARAM lp)
{
switch(msg)
{
case WM_CLOSE:
ShowWindow(hCtrl,SW_SHOWMINIMIZED);
break;
case WM_INITDIALOG:
{
HINSTANCE hCCdll=GetModuleHandle(TEXT("comctl32.dll"));
UINT n;
HWND w;
for(n=0;n<16;n++)
{
char tmp[16] = {0};
itoa(n,tmp,10);
CreateControl(0,hCtrl,TRACKBAR_CLASSA,0,TBS_VERT | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,40+n*28,36,18,80,hCCdll,VOL_ID+n);
CreateControl(0,hCtrl,"STATIC",tmp,0,46+n*28,25,8,8,0,0);
CreateControl(0,hCtrl,"Button",0,BS_AUTOCHECKBOX | WS_TABSTOP,43+28*n,120,9,8,0,MUTE_ID+n);
CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,138,26,12,0,INS_ID_P+n);
CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n);
CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,150,26,12,0,INS_ID_B1+n);
CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n+16);
CreateControl(WS_EX_CLIENTEDGE,hCtrl,"EDIT",0,ES_AUTOHSCROLL | ES_NUMBER,36+28*n,162,26,12,0,INS_ID_B2+n);
CreateControl(0,hCtrl,"msctls_updown32",0,UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,0,0,0,0,0,SPIN_ID+n+32);
}
w=GetDlgItem(hCtrl,IDC_TEMPO);
SendMessage(w,TBM_SETRANGE,0,MAKELONG(0,100));
SendMessage(w,TBM_SETPOS,1,tempo2ui(tempo));
do_ttext(hCtrl,tempo);
if (cfg_ctrl_x!=0x80000000 && cfg_ctrl_y!=0x80000000)
{
int max_x=GetSystemMetrics(SM_CXSCREEN)-10,max_y=GetSystemMetrics(SM_CYSCREEN)-10;
if (cfg_ctrl_x>max_x) cfg_ctrl_x=max_x;
if (cfg_ctrl_y>max_y) cfg_ctrl_y=max_y;
SetWindowPos(hCtrl,0,cfg_ctrl_x,cfg_ctrl_y,0,0,SWP_NOZORDER|SWP_NOSIZE);
}
for(n=0;n<16;n++)
{
w=GetDlgItem(hCtrl,VOL_ID+n);
SendMessage(w,TBM_SETRANGE,1,MAKELONG(0,0x7f));
SendMessage(w,TBM_SETPOS,1,0x7f-90);
}
SendDlgItemMessage(hCtrl,IDC_NOVOL,BM_SETCHECK,novol,0);
SetDlgItemTextA(hCtrl,IDC_SYSEX1,sysex1);
SetDlgItemTextA(hCtrl,IDC_SYSEX2,sysex2);
for(n=0;n<48;n++)
{
SendDlgItemMessage(hCtrl,SPIN_ID+n,UDM_SETRANGE,0,MAKELONG(127,0));
}
for(n=0;n<16;n++)
{
SendDlgItemMessage(hCtrl,INS_ID_P+n,EM_LIMITTEXT,3,0);
SendDlgItemMessage(hCtrl,INS_ID_B1+n,EM_LIMITTEXT,3,0);
SendDlgItemMessage(hCtrl,INS_ID_B2+n,EM_LIMITTEXT,3,0);
}
initialized=1;
}
break;
case WM_COMMAND:
{
UINT n;
if (HIWORD(wp)==0)
{
if (wp==IDC_SYSEX1_SEND || wp==IDC_SYSEX2_SEND)
{
UINT sl;
BYTE* s=read_sysex_edit(GetDlgItem(hCtrl,(wp==IDC_SYSEX1_SEND)?IDC_SYSEX1:IDC_SYSEX2) , &sl);
if (s)
{
_sysex(s,sl);
free(s);
}
}
else if (wp==IDC_NOVOL)
{
novol=SendMessage((HWND)lp,BM_GETCHECK,0,0);
}
else if (wp==IDC_NOINS)
{
noins=SendMessage((HWND)lp,BM_GETCHECK,0,0);
}
else if (wp==IDC_ALL_ON)
{
UINT n;
for(n=0;n<16;n++)
{
if (mute_mask&(1<<n))
{
SendDlgItemMessage(hCtrl,MUTE_ID+n,BM_SETCHECK,0,0);
set_mute(n,0);
}
}
}
else if (wp==IDC_ALL_OFF)
{
UINT n;
for(n=0;n<16;n++)
{
if (!(mute_mask&(1<<n)))
{
SendDlgItemMessage(hCtrl,MUTE_ID+n,BM_SETCHECK,1,0);
set_mute(n,1);
}
}
}
else if (wp==IDC_GMRESET)
{
rsysex(d_GMReset);
}
else if (wp==IDC_GSRESET)
{
rsysex(d_GSReset);
}
else if (wp==IDC_XGRESET)
{
rsysex(d_XGReset);
}
else for(n=0;n<16;n++)
{
if (wp==MUTE_ID+n)
{
set_mute(n,SendMessage((HWND)lp,BM_GETCHECK,0,0));
break;
}
}
}
else if (HIWORD(wp)==EN_CHANGE)
{
if (initialized)
{
wp&=0xFFFF;
UINT n;
for(n=0;n<16;n++)
{
if (wp==INS_ID_P+n)
{
UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F;
if (p!=ins_tab[n])
{
ins_tab[n]=p;
SEND_MSG(0xC0|n|(p<<8));
}
break;
}
else if (wp==INS_ID_B1+n)
{
UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F;
if (p!=ctrl_tab[n][0])
{
ctrl_tab[n][0]=p;
SEND_MSG(0xB0|n|(p<<16));
SEND_MSG(0xC0|n|(ins_tab[n]<<8));
}
break;
}
else if (wp==INS_ID_B2+n)
{
UINT p=GetDlgItemInt(hCtrl,wp,0,0)&0x7F;
if (p!=ctrl_tab[n][0x20])
{
ctrl_tab[n][0x20]=p;
SEND_MSG(0x20B0|n|(p<<16));
SEND_MSG(0xC0|n|(ins_tab[n]<<8));
}
break;
}
}
}
}
}
break;
case WM_VSCROLL:
{
HWND sb=(HWND)lp;
if (sb)
{
UINT id=GetWindowLong(sb,GWL_ID);
UINT n;
for(n=0;n<16;n++)
{
if (id==VOL_ID+n)
{
UINT val=0x7f-SendMessage(sb,TBM_GETPOS,0,0);
ctrl_tab[n][7]=val;
SEND_MSG(0x7B0|n|(val<<16));
break;
}
}
}
}
break;
case WM_HSCROLL:
tempo=ui2tempo(SendDlgItemMessage(hCtrl,IDC_TEMPO,TBM_GETPOS,0,0));
do_ttext(hCtrl,tempo);
break;
}
}
#endif
seq_base::~seq_base()
{
#ifdef SEQ_HAVE_PANEL
if (hCtrl)
{
cfg_ctrl_min=!!IsIconic(hCtrl);
RECT r;
GetWindowRect(hCtrl,&r);
cfg_ctrl_x=r.left;
cfg_ctrl_y=r.top;
GetDlgItemTextA(hCtrl,IDC_SYSEX1,sysex1,256);
GetDlgItemTextA(hCtrl,IDC_SYSEX2,sysex2,256);
DestroyWindow(hCtrl);
DeleteCriticalSection(&tm_sec);
}
g_tempo=tempo;
g_novol=novol;
g_noins=noins;
#endif
if (events) free(events);
}
seq_base::seq_base()
{
mf=0;
kill=0;paused=0;
smap=0;
pan=0;vol=0;
seek_to=0;
n_events=0;
events=0;
c_loop=0;
loop_start=0;
memset(&notes,0,sizeof(notes));
memset(&ctrl_tab,0,sizeof(ctrl_tab));
memset(&ins_tab,0,sizeof(ins_tab));
tm_ofs=0;
p_time=0;
hTrd=0;
ins_set=0;
#ifdef SEQ_HAVE_PANEL
hCtrl=0;
tempo=g_tempo;
novol=g_novol;
noins=g_noins;
last_time_ms=0;
last_time_ret=0;
mute_mask=0;
initialized=0;
#endif
}
#define GET_TIME get_time()//timeGetTime()
int IS_SPEC_C(int x) {return (x>=0x60 && x<=0x65) || x==6 || x==26 || x>=120;}
#define n_sysex smap->pos
DWORD seq_base::preprocess(DWORD e)
{
BYTE t=(BYTE)(e&0xF0);
if (t==0xB0)
{
UINT v=(e>>16)&0xFF;
BYTE c=(BYTE)(e>>8);
#ifdef SEQ_HAVE_PANEL
if (c==7)
{
if (mute_mask&(1<<(e&0xF))) v=0;
}
#endif
e=(e&0xFFFF)|((v&0xFF)<<16);
}
else if (t==0xC0)
{
ins_set|=1<<(e&0xF);
}
return e;
}
void seq_base::send_sysex(int n)
{
#ifdef USE_LOG
log_write("send_sysex()");
#endif
if (!smap || n>=n_sysex) return;
_sysex(smap->data+smap->events[n].ofs,smap->events[n].len);
}
/*
void seq_base::reset_ins()
{
UINT n;
for(n=0;n<16;n++)
{
cb->shortmsg(0xC0|n);
}
}
*/
BOOL seq_base::do_ctrl(DWORD e)
{
BYTE tp=(BYTE)(e&0xF0);
BYTE ch=(BYTE)(e&0x0F);
if (tp==0xC0)
{
#ifdef SEQ_HAVE_PANEL
if (noins) return 0;
#endif
//if (!cfg_fctrl && (e>>8)==ins_tab[e&0xF]) return 0;
UINT val=e>>8;
ins_tab[ch]=val;
#ifdef SEQ_HAVE_PANEL
if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_P+ch,val,0);
#endif
} else if (tp==0xB0)
{
UINT cn = (e>>8)&0x7F;
UINT val= (e>>16)&0x7F;
#ifdef SEQ_HAVE_PANEL
if (cn==0)
{
if (noins) return 0;
if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_B1+ch,val,0);
}
else if (cn==0x20)
{
if (noins) return 0;
if (hCtrl) SetDlgItemInt(hCtrl,INS_ID_B2+ch,val,0);
}
else if (cn==7)
{
if (novol) return 0;
if (hCtrl) PostMessage(GetDlgItem(hCtrl,VOL_ID+(e&0xF)),TBM_SETPOS,1,0x7F-val);
}
else if (cn==0x27)
{
if (novol) return 0;
}
#endif
if (!IS_SPEC_C(cn)) ctrl_tab[e&0xF][cn]=val;
}
else if (tp==0x90)
{
if (!(ins_set&(1<<ch)))
{
SEND_MSG(0xC0|ch);
}
}
return 1;
}
void seq_base::reset()
{
int not,ch;
for(ch=0;ch<16;ch++)
{
if (ctrl_tab[ch][0x40])
{
seq_shortmsg(0x40B0|ch);
ctrl_tab[ch][0x40]=0;
}
if (ch==9) continue;
for(not=0;not<128;not++)
{
if (note_state(ch,not))
{
seq_shortmsg((not<<8)|0x80|ch);
note_off(ch,not);
}
}
}
}
int seq_base::note_state(int ch,int note)
{
UINT pos=(ch<<7)+note;
return notes[pos>>3]&(1<<(pos&0x7));
}
void seq_base::note_on(int ch,int note)
{
UINT pos=(ch<<7)+note;
notes[pos>>3]|=(1<<(pos&0x7));
}
void seq_base::note_off(int ch,int note)
{
UINT pos=(ch<<7)+note;
notes[pos>>3]&=~(1<<(pos&0x7));
}
UINT seq_base::do_seek(DWORD n,DWORD p)
{
UINT m,c;
BYTE _ctrl_tab[16][128] = {0};
BYTE _ins_tab[16] = {0};
memcpy(_ctrl_tab,ctrl_tab,sizeof(_ctrl_tab));
memcpy(_ins_tab,ins_tab,sizeof(_ins_tab));
if (n==0)
{
memset(ins_tab,0,sizeof(ins_tab));
for(m=0;m<16;m++)
{
_ctrl_tab[m][0]=_ctrl_tab[m][0x20]=0;
}
}
while(n<n_events && p>events[n].tm)
{
DWORD e=events[n].ev;
if (!(e&0x80000000))
{
if (do_ctrl(e))
{
if (((e&0xF0)==0xB0) && IS_SPEC_C((e>>8)&0xFF))
{
seq_shortmsg(e);
}
}
}
n++;
}
for(c=0;c<16;c++)
{
for(m=0;m<128;m++)
{
if (!IS_SPEC_C(m) && _ctrl_tab[c][m]!=ctrl_tab[c][m])
{
SEND_MSG(((DWORD)ctrl_tab[c][m]<<16)|(m<<8)|0xB0|c);
}
}
if (_ins_tab[c]!=ins_tab[c])
{
SEND_MSG(((DWORD)ins_tab[c]<<8)|0xC0|c);
}
}
return n;
}
DWORD WINAPI seq_base::seq_trd(void* p)
{
((seq_base*)p)->thread();
return 0;
}
void seq_base::sysexfunc(seq_base* cb,BYTE* s,UINT sz)
{
cb->seq_sysex(s,sz);
}
void seq_base::thread()
{
tm_ofs=-1;
if (seq_play_start())
{
sysex_startup((SYSEXFUNC)sysexfunc,this);
tm_ofs=GET_TIME;
DWORD pos=0;
while(!kill)
{
DWORD c_t=GET_TIME-tm_ofs;
if (paused)
{
reset();
while(paused && !kill) MIDI_callback::Idle();
if (kill) break;
tm_ofs=GET_TIME-c_t;
}
if (seek_to!=-1)
{
_seek:
DWORD _p=seek_to > c_t ? pos : 0;
c_t=seek_to;
seek_to=-1;
tm_ofs=GET_TIME-c_t;
reset();
pos=c_t ? do_seek(_p,c_t) : 0;
}
if (events[pos].tm+1600 < c_t)
{
reset();
pos=do_seek(pos,c_t);
}
while(pos<n_events && events[pos].tm<=c_t && !kill)
{
DWORD e=events[pos++].ev;
if (e)
{
if (e&0x80000000)
{
send_sysex(e&0x7FFFFFFF);
}
else
{
if ((e&0xF0)==0x90)
{
note_on(e&0xf,(e>>8)&0xFF);
}
else if ((e&0xF0)==0x80)
{
note_off(e&0xf,(e>>8)&0xFF);
}
if (do_ctrl(e))
SEND_MSG(e);
}
}
}
if (pos>=n_events || c_t >= events[n_events-1].tm)
{
if (loop_start!=-1 && (--c_loop))
{
c_t=loop_start;
tm_ofs=GET_TIME-c_t;
pos=do_seek(0,c_t);
continue;
}
if (cfg_eof_delay)
{
DWORD t=timeGetTime();
do
{
MIDI_callback::Idle();
} while(!kill && seek_to==-1 && t+cfg_eof_delay>timeGetTime());
if (seek_to!=-1) {
pos=0;
goto _seek;
}
}
if (!kill) MIDI_core::Eof();
break;
}
if (kill) break;
MIDI_callback::Idle();
}
reset();
}
seq_play_stop();
}
int seq_base::gettime()
{
if (paused)
return (seek_to==-1) ? seek_to>>3 : p_time;
else
return (GET_TIME-tm_ofs)>>3;
}
int seq_base::settime(int tm)
{
seek_to=tm<<3;
return 1;
}
void seq_base::pause()
{
paused=1;
p_time=GET_TIME-tm_ofs;
}
void seq_base::unpause()
{
paused=0;
}
int seq_base::seq_cmd_start(DWORD cflags)
{
mf=MIDI_core::getFile();
#ifdef SEQ_HAVE_PANEL
mute_mask=0;
#endif
c_loop=cfg_loop_infinite ? -1 : cfg_loop_count;
memset(notes,0,sizeof(notes));
memset(ctrl_tab,-1,sizeof(ctrl_tab));
memset(ins_tab,0,sizeof(ins_tab));
UINT n;
for(n=0;n<16;n++) ctrl_tab[n][7]=90;
events=do_table(mf,8,&n_events,&loop_start,cflags);
if (!events) return 0;
if (!cfg_nosysex && mf->smap && mf->smap->pos)
{
smap=mf->smap;
}
else smap=0;
kill=0;
seek_to=-1;
paused=0;
#ifdef SEQ_HAVE_PANEL
if (cfg_seq_showpanel)
{
InitializeCriticalSection(&tm_sec);
WASABI_API_CREATEDIALOGPARAMW(IDD_EXT_IMM, MIDI_callback::GetMainWindow(), CtrlProc, (LPARAM)this);
ShowWindow(hCtrl,cfg_ctrl_min ? SW_SHOWMINIMIZED : SW_SHOW);
}
else
{
tempo=1;
novol=0;
noins=0;
}
#endif
DWORD id;
hTrd=CreateThread(0,0,seq_trd,this,CREATE_SUSPENDED,&id);
#ifndef _DEBUG
SetThreadPriority(hTrd,THREAD_PRIORITY_TIME_CRITICAL);
#endif
ResumeThread(hTrd);
return 1;
}
void seq_base::seq_cmd_stop()
{
#ifdef USE_LOG
log_write("stopping sequencer");
#endif
if (hTrd)
{
#ifdef USE_LOG
log_write("killing thread");
#endif
kill=1;
if (WaitForSingleObject(hTrd,4000)!=WAIT_OBJECT_0)
{
#ifdef USE_LOG
log_write("unable to kill thread");
#endif
TerminateThread(hTrd,0);
}
#ifdef USE_LOG
else log_write("thread killed normally");
#endif
CloseHandle(hTrd);
}
}
/*
void seq_base::enum_ins()
{
DWORD ttab[256];
memset(ttab,-1,sizeof(ttab));
UINT tpt=0;
UINT n;
DWORD c_ins[16];
memset(c_ins,0,sizeof(c_ins));
c_ins[9]=0x80000000;
for(n=0;n<n_events;n++)
{
DWORD t=events[n].ev;
if (t&0xFF000000) continue;
UINT c=t&0xF0;
UINT ch=events[n].ev&0xF;
if ((t&0xFFF0)==0x20B0)
{
c_ins[ch]=(c_ins[ch]&0xFFFF00FF)|((t>>8)&0xFF00);
}
else if ((t&0xFFF0)==0xB0)
{
c_ins[ch]=(c_ins[ch]&0xFF00FFFF)|(t&0xFF0000);
}
else if ((t&0xF0)==0xC0)
{
c_ins[ch]=(c_ins[ch]&0xFFFFFF00)|((t>>8)&0xFF);
}
else if ((t&0xF0)==0x90)
{
UINT n;
for(n=0;n<256;n++)
{
if (ttab[n]==c_ins[ch]) goto ok;
}
cb->enum_ins(ttab[tpt]=c_ins[ch]);
tpt=(tpt+1)&0xFF;
ok:;
}
}
}
*/