#define PSMR_FLAT		-8
#define PSMR_SHARP		-7
#define PSMR_TIE		-6
#define PSMR_REST		-5
#define PSMR_INS_NOTE		-4
#define PSMR_DELETE_NOTE	-3
#define PSMR_SET_WORD		-2

F64 PopUpDuration()
{
  I64 i;
  CDoc *doc=DocNew;
  DocPrint(doc,"$$GREEN$$$$MU,\"Set Word\",LE=PSMR_SET_WORD$$\n"
	"$$MU,\"Toggle Sharp\",LE=PSMR_SHARP$$\n"
	"$$MU,\"Toggle Flat\",LE=PSMR_FLAT$$\n"
	"$$MU,\"Toggle Tie\",LE=PSMR_TIE$$\n"
	"$$MU,\"Make Rest\",LE=PSMR_REST$$\n"
	"$$MU,\"Insert Note\",LE=PSMR_INS_NOTE$$\n"
	"$$MU,\"Delete Note\",LE=PSMR_DELETE_NOTE$$\n\n");
  for (i=0;i<PSM_DURATIONS_NUM;i++)
    DocPrint(doc,"$$MU,\"%7.5f\",LE=%d$$\n",psm_durations[i],i);
  DocPrint(doc,"\n$$MU,\"CANCEL\",LE=DOCM_CANCEL$$\n");
  i=PopUpMenu(doc);
  DocDel(doc);
  return i;
}

U0 PsmRightClick(I64 x,I64 y)
{
  U8 *st,*st2;
  PsmNote *tmpn,*tmpn1;
  I64 i,old_doc_flags;
  if (DocPut) old_doc_flags=DocPut->flags;
  psm.cur_note=tmpn=PsmFindNote(x,y);
  if (tmpn!=&psm.head) {
    Fs->win_inhibit=WIG_USER_TASK_DFT;
    i=PopUpDuration;
    if (0<=i<PSM_DURATIONS_NUM) {
      if (tmpn->type==PSMT_NOTE)
	tmpn->duration=i;
    } else {
      switch (i) {
	case PSMR_REST:
	  if (tmpn->type==PSMT_NOTE)
	    tmpn->ona=0;
	  break;
	case PSMR_SHARP:
	  if (tmpn->type==PSMT_NOTE && tmpn->ona) {
	    if (Btr(&tmpn->flags,PSMf_FLAT))
	      tmpn->ona++;
	    if (Btc(&tmpn->flags,PSMf_SHARP))
	      tmpn->ona--;
	    else
	      tmpn->ona++;
	  }
	  break;
	case PSMR_FLAT:
	  if (tmpn->type==PSMT_NOTE && tmpn->ona) {
	    if (Btr(&tmpn->flags,PSMf_SHARP))
	      tmpn->ona--;
	    if (Btc(&tmpn->flags,PSMf_FLAT))
	      tmpn->ona++;
	    else
	      tmpn->ona--;
	  }
	  break;
	case PSMR_TIE:
	  if (tmpn->type==PSMT_NOTE)
	    Btc(&tmpn->flags,PSMf_TIE);
	  break;
	case PSMR_SET_WORD:
	  if (tmpn->type==PSMT_NOTE) {
	    if (DocPut) DocPut->flags&=~DOCF_FORM;
	    if (PsmHasWords(tmpn->word))
	      st2=MStrPrint("\nWord(\"%Q\"):",tmpn->word);
	    else
	      st2=MStrPrint("\nWord(\"\"):");
	    DocBottom;
	    st=GetStr(st2);
	    Free(st2);
	    Free(tmpn->word);
	    if (*st) {
	      tmpn->word=MStrPrint("%q",st);
	      Free(st);
	    } else
	      tmpn->word=StrNew("");
	    if (DocPut) DocPut->flags=DocPut->flags&
		    ~DOCF_FORM|old_doc_flags&DOCF_FORM;
	  }
	  break;
	case PSMR_INS_NOTE:
	  tmpn1=PsmNoteCopy(tmpn);
	  QueIns(tmpn1,tmpn);
	  break;
	case PSMR_DELETE_NOTE:
	  psm.cur_note=tmpn->next;
	  QueRem(tmpn);
	  PsmNoteDel(tmpn);
	  break;
      }
    }
    PsmSetWidth(psm.cur_note);
    Fs->win_inhibit=WIG_TASK_DFT-WIF_SELF_FOCUS
	  -WIF_SELF_BORDER-WIF_FOCUS_TASK_MENU-WIF_SELF_CTRLS;
  }
}

U0 PsmLeftClickPickNoteBox(I64 duration)
{
  I64 o,n,msg_code,arg1,arg2;
  PsmNote *tmpn,*tmpn1;
  do {
    msg_code=GetMsg(&arg1,&arg2,1<<MSG_MS_L_UP|1<<MSG_MS_MOVE);
    if (msg_code==MSG_MS_MOVE) {
      DrawDC2;
      DrawNote(psm.dc2,arg1,arg2,duration);
    }
  } while (msg_code!=MSG_MS_L_UP);
  if (arg2<13*FONT_HEIGHT) {
    if (arg1>psm.head.last->x)
      tmpn1=psm.head.last;
    else if (arg1<psm.head.next->x)
      tmpn1=&psm.head;
    else
      tmpn1=PsmFindNote(arg1-PSM_NOTE_SPACING/2,arg2);
    tmpn=CAlloc(sizeof(PsmNote));
    tmpn->type=PSMT_NOTE;
    arg2=arg2/4-15;
    n=arg2%7;
    o=4+arg2/-7;
    if (n<0) {
      n+=7;
      o++;
    }
    n=psm_note_inverse_map[n];
    if (n<3)
      o--;
    tmpn->ona=Note2Ona(n,o);
    tmpn->duration=duration;
    PsmSetWidth(tmpn);
    QueIns(tmpn,tmpn1);
    psm.cur_note=tmpn->next;
  }
  DrawDC2;
}

U0 PsmLeftClickPickMeterBox(I64 top,I64 bottom)
{
  I64 msg_code,arg1,arg2;
  PsmNote *tmpn,*tmpn1;
  do {
    msg_code=GetMsg(&arg1,&arg2,1<<MSG_MS_L_UP|1<<MSG_MS_MOVE);
    if (msg_code==MSG_MS_MOVE) {
      DrawDC2;
      DrawTimeSignature(psm.dc2,arg1,arg2,top,bottom);
    }
  } while (msg_code!=MSG_MS_L_UP);
  if (arg2<13*FONT_HEIGHT) {
    if (arg1>=psm.head.x)
      tmpn1=psm.head.last;
    else if (arg1<psm.head.next->x)
      tmpn1=&psm.head;
    else
      tmpn1=PsmFindNote(arg1-PSM_NOTE_SPACING/2,arg2);
    tmpn=CAlloc(sizeof(PsmNote));
    tmpn->type=PSMT_METER;
    tmpn->meter_top=top;
    tmpn->meter_bottom=bottom;
    PsmSetWidth(tmpn);
    QueIns(tmpn,tmpn1);
    psm.cur_note=tmpn->next;
  }
  DrawDC2;
}

U0 PsmLeftClickStaffPtr(I64 x,I64 y)
{
  PsmNote *tmpn,*tmpn1;
  I64 o,n,msg_code,arg1,arg2,n_original,o_original;
  psm.cur_note=tmpn=PsmFindNote(x,y);
  if (tmpn!=&psm.head) {
    if (tmpn->type==PSMT_NOTE) {
      o_original=Ona2Octave(tmpn->ona);
      n_original=Ona2Note  (tmpn->ona);
      do {
	msg_code=GetMsg(&arg1,&arg2,1<<MSG_MS_L_UP|1<<MSG_MS_MOVE);
	if (msg_code==MSG_MS_L_UP) {
	  tmpn1=PsmFindNote(arg1,arg2);
	  if (tmpn1==&psm.head || tmpn1==tmpn)
	    goto move_note;
	  else {
	    Free(tmpn1->word);
	    tmpn1->word=tmpn->word;
	    tmpn->word=NULL;
	    tmpn->ona=Note2Ona(n_original,o_original);
	  }
	} else {
move_note:
	  arg2=arg2/4-15;
	  n=arg2%7;
	  o=4+arg2/-7;
	  if (n<0) {
	    n+=7;
	    o++;
	  }
	  n=psm_note_inverse_map[n];
	  if (n<3)
	    o--;
	  tmpn->ona=Note2Ona(n,o);
	}
      } while (msg_code!=MSG_MS_L_UP);
      PsmSetWidth(tmpn);
    }
  }
}

U0 PsmLeftClickStaffBox(I64 x,I64 y)
{
  I64 msg_code,arg1,arg2;
  do {
    msg_code=GetMsg(&arg1,&arg2,1<<MSG_MS_L_UP|1<<MSG_MS_MOVE);
    DrawDC2;
    psm.dc2->color=ROPF_DITHER+WHITE<<16+BLACK;
    GrBorder(psm.dc2,x,y,arg1,arg2);
    if (msg_code==MSG_MS_L_UP) {
      if (x>arg1) SwapI64(&x,&arg1);
      PsmMarkSel(x,arg1,TRUE);
    }
  } while (msg_code!=MSG_MS_L_UP);
  DrawDC2;
}

U0 PsmLeftClick(I64 x,I64 y)
{
  I64 duration,top,bottom;
  if (y<13*FONT_HEIGHT) {
    if (psm.tool==PSMTT_PTR_TOOL)
      PsmLeftClickStaffPtr(x,y);
    else
      PsmLeftClickStaffBox(x,y);
  } else {
    duration=PsmGetPickNoteBoxDuration(x,y);
    if (0<=duration<PSM_DURATIONS_NUM)
      PsmLeftClickPickNoteBox(duration);
    else if (PsmGetPickMeterBox(x,y,&top,&bottom))
      PsmLeftClickPickMeterBox(top,bottom);
    else if (PsmGetPickToolBox(x,y))
      DrawDC2;
  }
}

U8 PsmCvtDuration(F64 d)
{
  F64 d1,d2;
  I64 j;
  for (j=0;j<PSM_DURATIONS_NUM;j++) {
    d1=psm_durations[j];
    d2=psm_durations[j+1];
    if (d<d1*d2/(d1+d2))
      return j;
  }
  return 0;
}

#define PSM_KEYS_NUM	20
class PsmKey
{
  U8 x,w,h,ascii;
};

#define PSM_W_W	16
#define PSM_W_H	36
#define PSM_B_W	8
#define PSM_B_H	20

PsmKey psm_kbd[PSM_KEYS_NUM]={
  { 2*PSM_W_W-4,PSM_B_W,PSM_B_H,'e' },
  { 3*PSM_W_W-4,PSM_B_W,PSM_B_H,'r' },
  { 4*PSM_W_W-4,PSM_B_W,PSM_B_H,'t' },
  { 6*PSM_W_W-4,PSM_B_W,PSM_B_H,'u' },
  { 7*PSM_W_W-4,PSM_B_W,PSM_B_H,'i' },
  { 9*PSM_W_W-4,PSM_B_W,PSM_B_H,'p' },
  {10*PSM_W_W-4,PSM_B_W,PSM_B_H,'[' },
  {11*PSM_W_W-4,PSM_B_W,PSM_B_H,']' },

  { 0*PSM_W_W,PSM_W_W,PSM_W_H,'a' },
  { 1*PSM_W_W,PSM_W_W,PSM_W_H,'s' },
  { 2*PSM_W_W,PSM_W_W,PSM_W_H,'d' },
  { 3*PSM_W_W,PSM_W_W,PSM_W_H,'f' },
  { 4*PSM_W_W,PSM_W_W,PSM_W_H,'g' },
  { 5*PSM_W_W,PSM_W_W,PSM_W_H,'h' },
  { 6*PSM_W_W,PSM_W_W,PSM_W_H,'j' },
  { 7*PSM_W_W,PSM_W_W,PSM_W_H,'k' },
  { 8*PSM_W_W,PSM_W_W,PSM_W_H,'l' },
  { 9*PSM_W_W,PSM_W_W,PSM_W_H,';' },
  {10*PSM_W_W,PSM_W_W,PSM_W_H,'\'' },
  {11*PSM_W_W,PSM_W_W,PSM_W_H,'\n'},
};

U0 PsmDownKey(I64 x,I64 y)
{
  I64 i;
  PsmKey *o;
  y-=FONT_HEIGHT*13;
  if (0<=y<PSM_W_H) {
    x-=16;
    for (i=0;i<PSM_KEYS_NUM;i++) {
      o=&psm_kbd[i];
      if (o->x<=x<o->x+o->w && y<o->h) {
	Msg(MSG_KEY_DOWN,o->ascii,0);
	return;
      }
    }
  }
}

U0 PsmUpKey(I64 x,I64 y)
{
  I64 i;
  PsmKey *o;
  y-=FONT_HEIGHT*13;
  if (0<=y<PSM_W_H) {
    x-=16;
    for (i=0;i<PSM_KEYS_NUM;i++) {
      o=&psm_kbd[i];
      if (o->x<=x<o->x+o->w && y<o->h) {
	Msg(MSG_KEY_UP,o->ascii,0);
	return;
      }
    }
  }
}

U0 PsmPushMode(I64 psm_octave)
{
  Fs->win_inhibit=WIG_TASK_DFT-WIF_SELF_FOCUS
	-WIF_SELF_BORDER-WIF_FOCUS_TASK_MENU-WIF_SELF_CTRLS;
  PsmMenu(psm_octave);
}

U0 PsmPopMode()
{
  Fs->win_inhibit=WIG_USER_TASK_DFT;
  DCFill;
}

#define PSMF_CD		1
#define PSMF_INCOMPLETE	2

U0 Psalmody(U8 *dirname="~/Psalmody")
{
  Bool was_playing,is_null=TRUE,was_null=TRUE;
  I64 arg1,arg2,msg_code=0,col,ona=0,last_ona=0,
	psm_octave=4,timeout_val,timeout_val2,old_doc_flags;
  U8 *filename=NULL,*st,*st2;
  PsmNote *tmpn;
  F64 psm_duration=1.0,d,evt_time=tS,note_down_time=tS;
  CCtrl *c=TempoNew;

  if (DocPut) old_doc_flags=DocPut->flags;
  SettingsPush; //See $LK,"SettingsPush",A="MN:SettingsPush"$

  MusicSettingsRst;
  tempo_state.tempo=Round(TEMPO_RANGE*(music.tempo-0.5)/4.4);
  tempo_state.stacatto=Round(TEMPO_RANGE*(music.stacatto_factor-0.12)/0.88);

  if (DocPut) DocPut->flags|=DOCF_FORM;

  MemSet(&psm,0,sizeof(PsmCtrl));
  psm.scrn_x=0;
  psm.head.next=psm.head.last=&psm.head;
  psm.clip.next=psm.clip.last=&psm.clip;
  psm.cur_note=&psm.head;
  psm.dc2=DCAlias;

  MenuPush(
	"File {"
	"  New(,'.');"
	"  ChgDir(MSG_CMD,PSMF_CD);"
	"  Open(,CH_CTRLO);"
	"  SaveAs(,CH_CTRLA);"
	"  Abort(,CH_SHIFT_ESC);"
	"  Exit(,CH_ESC);"
	"}"
	"Edit {"
	"  Cut(,CH_CTRLX);"
	"  Copy(,CH_CTRLC);"
	"  Paste(,CH_CTRLV);"
	"  RightMenu(,'\n');"
	"  BackSpace(,CH_BACKSPACE);"
	"  DeleteNote(,,SC_DELETE);"
	"  ClearSong(,'.');"
	"  Left(,,SC_CURSOR_LEFT);"
	"  Right(,,SC_CURSOR_RIGHT);"
	"  GoBegin(,,0x4CB0000044B);"
	"  GoEnd(,,0x4CD0000044D);"
	"}"
	"Song {"
	"  Play(,'x');"
	"  Record(,'z');"
	"  Random(,',');"
	"  MarkIncomplete(MSG_CMD,PSMF_INCOMPLETE);"
	"}"
	"Snd {"
	"  Octave1(,'1');"
	"  Octave2(,'2');"
	"  Octave3(,'3');"
	"  Octave4(,'4');"
	"  Octave5(,'5');"
	"  Octave6(,'6');"
	"  Octave7(,'7');"
	"}"
	"Help {"
	"  Help(,,SC_F1);"
	"}"
	);
  psm.incomplete_entry=MenuEntryFind(Fs->cur_menu,"Song/MarkIncomplete");
  psm.record_entry=MenuEntryFind(Fs->cur_menu,"Song/Record");

  AutoComplete;
  WinBorder;
  WinMax;

  dirname=StrNew(dirname);
  PsmPushMode(psm_octave);
  col=0;
  Fs->draw_it=&DrawIt;

  try {
    while (TRUE) {
      was_playing=FALSE;
mo_start:
      if (ms.pos_text.y-Fs->win_top<18)
	msg_code=GetMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP|
	      1<<MSG_MS_L_DOWN|1<<MSG_MS_L_UP|1<<MSG_MS_R_UP|
	      1<<MSG_MS_MOVE|1<<MSG_CMD);
      else
	msg_code=GetMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP|
	      1<<MSG_MS_MOVE|1<<MSG_CMD);
mo_got_msg:
      if (msg_code==MSG_KEY_DOWN && arg1==CH_SPACE && !arg2) {
//The Window Mgr sets the Doc cur_entry to a bttn
	//and generates a <SPACE> when the Doc Bttns are clicked.
	//This is so that kbd and mouse are the same for Doc's.
	//We must now pass the <SPACE> onto the Doc hndlr.
	PutKey(arg1,arg2);
	goto mo_start;
      }
      if (msg_code!=MSG_MS_MOVE) {
	DocBottom;
	if (was_playing || DocPut->cur_entry->y>=Fs->win_height-2) {
	  PsmMenu(psm_octave);
	  col=0;
	}
      }

      ona=Note2Ona(3,psm_octave+1); //C
      is_null=TRUE;
      switch (msg_code) {
	case MSG_CMD:
	  PsmPopMode;
	  switch (arg1) {
	    case PSMF_CD:
	      st2=dirname;
	      if (dirname=PopUpPickDir) {
		Free(st2);
		Free(filename);
		filename=NULL;
	      } else
		dirname=st2;
	      break;
	    case PSMF_INCOMPLETE:
	      psm.incomplete_entry->checked=!psm.incomplete_entry->checked;
	      break;
	  }
	  PsmPushMode(psm_octave);
	  col=0;
	  break;
	case MSG_KEY_DOWN:
	  evt_time=tS;
	  if ('0'<=arg1<='9') {
	    psm_octave=arg1-'0';
	    PsmMenu(psm_octave);
	    col=0;
	  } else {
	    switch (arg1) {
	      start:
		case 'a':	ona-=8;		break;
		case 's':	ona-=7;		break;
		case 'e':	ona-=6;		break;
		case 'd':	ona-=5;		break;
		case 'r':	ona-=4;		break;
		case 'f':	ona-=3;		break;
		case 't':	ona-=2;		break;
		case 'g':	ona--;		break;
		case 'h':			break;
		case 'u':	ona++;		break;
		case 'j':	ona+=2;		break;
		case 'i':	ona+=3;		break;
		case 'k':	ona+=4;		break;
		case 'l':	ona+=5;		break;
		case 'p':	ona+=6;		break;
		case ';':	ona+=7;		break;
		case '[':	ona+=8;		break;
		case '\'':	ona+=9;		break;
		case ']':	ona+=10;	break;
		case CH_SPACE:	ona=0;		break;
	      end:
		is_null=FALSE;
		break;

	      case 0:
		switch (arg2.u8[0]) {
		  case SC_CURSOR_LEFT:
		    if (arg2&SCF_CTRL) {
		      while (psm.cur_note->last!=&psm.head) {
			psm.cur_note=psm.cur_note->last;
			if (psm.cur_note!=&psm.head)
			  LBEqu(&psm.cur_note->flags,PSMf_SEL,arg2&SCF_SHIFT);
		      }
		    } else {
		      if (psm.cur_note->last!=&psm.head) {
			psm.cur_note=psm.cur_note->last;
			if (psm.cur_note!=&psm.head)
			  LBEqu(&psm.cur_note->flags,PSMf_SEL,arg2&SCF_SHIFT);
		      }
		    }
		    break;
		  case SC_CURSOR_RIGHT:
		    if (arg2&SCF_CTRL) {
		      while (psm.cur_note!=&psm.head) {
			if (psm.cur_note!=&psm.head)
			  LBEqu(&psm.cur_note->flags,PSMf_SEL,arg2&SCF_SHIFT);
			psm.cur_note=psm.cur_note->next;
		      }
		    } else {
		      if (psm.cur_note!=&psm.head) {
			if (psm.cur_note!=&psm.head)
			  LBEqu(&psm.cur_note->flags,PSMf_SEL,arg2&SCF_SHIFT);
			psm.cur_note=psm.cur_note->next;
		      }
		    }
		    break;
		  case SC_DELETE:
		    if (arg2&SCF_SHIFT)
		      PsmCutToClip;
		    else {
		      tmpn=psm.cur_note;
		      psm.cur_note=tmpn->next;
		      if (tmpn!=&psm.head) {
			QueRem(tmpn);
			PsmNoteDel(tmpn);
		      }
		    }
		    break;
		  case SC_INS:
		    if (arg2&SCF_SHIFT)
		      PsmPasteClip;
		    else if (arg2&SCF_CTRL)
		      PsmCopyToClip;
		    break;
		  case SC_F1:
		    PsmPopMode;
		    PopUpEd("::/Apps/Psalmody/Help.DD.Z",Fs);
		    PsmPushMode(psm_octave);
		    col=0;
		    break;
		}
		break;
	      case ',':
		Free(filename);
		filename=NULL;
		PsmPopMode;
		music.octave=psm_octave;
		if (st2=GodSongStr) {
		  PsmLoadSongStr(st2,&psm_octave,&psm_duration);
		  Free(st2);
		}
		PsmPushMode(psm_octave);
		col=0;
		break;
	      case CH_CTRLO:
		PsmPopMode;
		RegOneTimePopUp(ARf_PSALMODY_JUKEBOX,
		      "Sel a song and preview it.\n"
		      "$$GREEN$$<SHIFT-ESC>$$FG$$ to load it into Psalmody.\n\n"
		      ST_WARN_ST " Graphics and other embelishments\n"
		      "will be lost because Psalmody can't\n"
		      "parse HolyC programs completely.\n");
		Free(filename);
		filename=NULL;
		JukeBox(dirname,&filename);
		if (filename) {
		  psm.scrn_x=0;
		  psm_duration=1.0;
		  psm_octave=4;
		  PsmSongDel(&psm.head);
		  psm.cur_note=&psm.head;
		  PsmLoadSong(filename,&psm_octave,&psm_duration);
		  psm.record_entry->checked=FALSE;
		  psm.cur_note=psm.head.next;
		}
		PsmPushMode(psm_octave);
		col=0;
		break;
	      case CH_CTRLA:
		PsmPopMode;
		filename=PsmSaveSong(dirname,filename);
		PsmPushMode(psm_octave);
		break;
	      case CH_CTRLC:
		PsmCopyToClip;
		break;
	      case CH_CTRLV:
		PsmPasteClip;
		break;
	      case CH_CTRLX:
		PsmCutToClip;
		break;
	      case '.':
		PsmMenu(psm_octave);
		col=0;
		Free(filename);
		filename=NULL;
		psm_duration=1.0;
		psm_octave=4;
		PsmSongDel(&psm.head);
		psm.cur_note=&psm.head;
		psm.scrn_x=0;
		break;
	      case '\n':
		if (psm.cur_note!=&psm.head)
		  PsmRightClick(psm.cur_note->x,psm.cur_note->y);
		break;
	      case 'x':
		if (was_playing)
		  break;
		col=0;
		psm.playing=TRUE;
		PsmMenu(psm_octave);
		tmpn=psm.cur_note;
		while (tmpn!=&psm.head) {
		  if (tmpn->type!=PSMT_METER) {
		    timeout_val=cnts.jiffies;
		    if (ms.pos_text.y-Fs->win_top<18)
		      msg_code=ScanMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|
			    1<<MSG_MS_L_DOWN|1<<MSG_MS_R_UP|1<<MSG_CMD);
		    else
		      msg_code=ScanMsg(&arg1,&arg2,1<<MSG_KEY_DOWN|
			    1<<MSG_MS_L_DOWN|1<<MSG_CMD);
		    if (msg_code) {
		      Snd;
		      psm.playing=FALSE;
		      was_playing=TRUE;
		      if (ms.pos_text.y-Fs->win_top>=18 &&
			    msg_code==MSG_MS_L_DOWN)
			goto mo_start;
		      else
			goto mo_got_msg;
		    }
		    psm.cur_note=tmpn;
		    psm.scrn_x+=tmpn->x-0.33*GR_WIDTH;
		    if (PsmHasWords(tmpn->word))
		      "%s",tmpn->word;
		    Snd(tmpn->ona);

		    music.tempo=4.4*tempo_state.tempo/TEMPO_RANGE+0.5;
		    music.stacatto_factor=
			  0.88*tempo_state.stacatto/TEMPO_RANGE+0.12;
		    d=JIFFY_FREQ*psm_durations[tmpn->duration]/music.tempo;
		    if (Bt(&tmpn->flags,PSMf_TIE)) {
		      timeout_val+=d;
		      timeout_val2=timeout_val;
		    } else {
		      timeout_val+=d*music.stacatto_factor;
		      timeout_val2=timeout_val+
			    d*(1.0-music.stacatto_factor);
		    }
		    SleepUntil(timeout_val);
		    Snd;
		    SleepUntil(timeout_val2);
		  }
		  tmpn=tmpn->next;
		}
		psm.cur_note=&psm.head;
		psm.scrn_x+=psm.cur_note->x-GR_WIDTH/2;
		psm.playing=FALSE;
		PsmMenu(psm_octave);
		col=0;
		Snd;
		break;
	      case CH_BACKSPACE:
		tmpn=psm.cur_note->last;
		if (tmpn!=&psm.head) {
		  QueRem(tmpn);
		  PsmNoteDel(tmpn);
		}
		if (col) {
		  '' CH_BACKSPACE;
		  col--;
		}
		break;
	      case 'z':
		if (psm.record_entry->checked)
		  psm.record_entry->checked=FALSE;
		else {
		  psm.record_entry->checked=TRUE;
		  psm_duration=1.0;
		  psm_octave=4;
		  psm.scrn_x=0;
		}
		PsmMenu(psm_octave);
		col=0;
		break;
	      case CH_ESC:
		PsmPopMode;
		filename=PsmSaveSong(dirname,filename);
		PsmPushMode(psm_octave);
	      case CH_SHIFT_ESC:
		goto mo_done;
	    }
	  }
	  break;
	case MSG_KEY_UP:
	  evt_time=tS;
	  break;
	case MSG_MS_MOVE:
	  if (arg2>18*FONT_HEIGHT)
	    Fs->win_inhibit=WIG_USER_TASK_DFT;
	  else
	    Fs->win_inhibit=WIG_TASK_DFT-WIF_SELF_FOCUS-WIF_SELF_BORDER
		  -WIF_FOCUS_TASK_MENU-WIF_SELF_CTRLS;
	  break;
	case MSG_MS_L_DOWN:
	  PsmDownKey(arg1,arg2);
	  PsmLeftClick(arg1,arg2);
	  break;
	case MSG_MS_L_UP:
	  PsmUpKey(arg1,arg2);
	  break;
	default:
	  PsmRightClick(arg1,arg2);
      }
      if (is_null)
	ona=0;
      if (ona!=last_ona || is_null!=was_null) {
	if (!ona) {
	  if (is_null)
	    st="";
	  else
	    st="R";
	} else
	  st=LstSub(Ona2Note(ona),psm_note_lst);
	Snd(ona);
	if (psm.record_entry->checked) {
	  if (!was_null) {
	    music.tempo=4.4*tempo_state.tempo/TEMPO_RANGE+0.5;
	    music.stacatto_factor=0.88*tempo_state.stacatto/TEMPO_RANGE+0.12;
	    tmpn->duration=PsmCvtDuration(
		  music.tempo*(evt_time-note_down_time));
	    PsmSetWidth(tmpn);
	    QueIns(tmpn,psm.cur_note->last);
	  }
	  if (!is_null) {
	    note_down_time=tS;
	    tmpn=CAlloc(sizeof(PsmNote));
	    tmpn->type=PSMT_NOTE;
	    tmpn->ona=ona;
	    if (st[1]=='#')
	      Bts(&tmpn->flags,PSMf_SHARP);
	  }
	}
	last_ona=ona;
	was_null=is_null;
	"%s",st;
	col+=StrLen(st);
	if (col>=Fs->win_width-1) {
	  '\n';
	  col=0;
	}
      }
    }
mo_done:
    GetMsg(,,1<<MSG_KEY_UP);
  } catch
    PutExcept;
  PsmPopMode;
  PsmSongDel(&psm.head);
  PsmSongDel(&psm.clip);
  TempoDel(c);
  DCFill;
  DCDel(psm.dc2);
  DocClear;
  SettingsPop;
  if (DocPut) DocPut->flags=DocPut->flags&~DOCF_FORM|old_doc_flags&DOCF_FORM;
  Free(dirname);
  MenuPop;
}
