#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_DEFAULT;
        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 = StrGet(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);
                    QueueInsert(tmpn1, tmpn);
                    break;

                case PSMR_DELETE_NOTE:
                    psm.cur_note = tmpn->next;
                    QueueRemove(tmpn);
                    PsmNoteDel(tmpn);
                    break;
            }
        }
        PsmSetWidth(psm.cur_note);
        Fs->win_inhibit = WIG_TASK_DEFAULT - WIF_SELF_FOCUS - WIF_SELF_BORDER - WIF_FOCUS_TASK_MENU - WIF_SELF_CTRLS;
    }
}

U0 PsmLeftClickPickNoteBox(I64 duration)
{
    I64      o, n, message_code, arg1, arg2;
    PsmNote *tmpn, *tmpn1;

    do
    {
        message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP | 1 << MESSAGE_MS_MOVE);
        if (message_code == MESSAGE_MS_MOVE)
        {
            DrawDC2;
            DrawNote(psm.dc2, arg1, arg2, duration);
        }
    }
    while (message_code != MESSAGE_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);
        QueueInsert(tmpn, tmpn1);
        psm.cur_note = tmpn->next;
    }
    DrawDC2;
}

U0 PsmLeftClickPickMeterBox(I64 top, I64 bottom)
{
    I64      message_code, arg1, arg2;
    PsmNote *tmpn, *tmpn1;

    do
    {
        message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP | 1 << MESSAGE_MS_MOVE);
        if (message_code == MESSAGE_MS_MOVE)
        {
            DrawDC2;
            DrawTimeSignature(psm.dc2, arg1, arg2, top, bottom);
        }
    }
    while (message_code != MESSAGE_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);
        QueueInsert(tmpn, tmpn1);
        psm.cur_note = tmpn->next;
    }
    DrawDC2;
}

U0 PsmLeftClickStaffPtr(I64 x, I64 y)
{
    PsmNote *tmpn, *tmpn1;
    I64      o, n, message_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
            {
                message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP | 1 << MESSAGE_MS_MOVE);
                if (message_code == MESSAGE_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 (message_code != MESSAGE_MS_L_UP);

            PsmSetWidth(tmpn);
        }
    }
}

U0 PsmLeftClickStaffBox(I64 x, I64 y)
{
    I64 message_code, arg1, arg2;

    do
    {
        message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP | 1 << MESSAGE_MS_MOVE);
        DrawDC2;
        psm.dc2->color = ROPF_DITHER + WHITE << 16 + BLACK;
        GrBorder(psm.dc2, x, y, arg1, arg2);
        if (message_code == MESSAGE_MS_L_UP)
        {
            if (x > arg1)
                SwapI64(&x, &arg1);
            PsmMarkSel(x, arg1, TRUE);
        }
    }
    while (message_code != MESSAGE_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 PsmConvertDuration(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)
            {
                Message(MESSAGE_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)
            {
                Message(MESSAGE_KEY_UP, o->ascii, 0);
                return;
            }
        }
    }
}

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

U0 PsmPopMode()
{
    Fs->win_inhibit = WIG_USER_TASK_DEFAULT;
    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, message_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, event_time = tS, note_down_time = tS;
    CCtrl   *c = TempoNew;

    if (DocPut)
        old_doc_flags = DocPut->flags;
    SettingsPush; //See SettingsPush

    MusicSettingsReset;
    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.screen_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(,'.');"
                "  ChangeDir(MESSAGE_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(MESSAGE_CMD,PSMF_INCOMPLETE);"
                "}"
                "Sound {"
                "  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 (mouse.pos_text.y - Fs->win_top < 18)
                message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN   | 1 << MESSAGE_KEY_UP   |
                                                        1 << MESSAGE_MS_L_DOWN  | 1 << MESSAGE_MS_L_UP  |
                                                        1 << MESSAGE_MS_R_UP    | 1 << MESSAGE_MS_MOVE  | 1 << MESSAGE_CMD);
            else
                message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN   | 1 << MESSAGE_KEY_UP |
                                                        1 << MESSAGE_MS_MOVE    | 1 << MESSAGE_CMD);
mo_got_message:
            if (message_code == MESSAGE_KEY_DOWN && arg1 == CH_SPACE && !arg2)
            {
                //The Window Mgr sets the Doc cur_entry to a button
                //and generates a <SPACE> when the Doc Buttons are clicked.
                //This is so that kbd and mouse are the same for Doc's.
                //We must now pass the <SPACE> onto the Doc handler.
                PutKey(arg1, arg2);
                goto mo_start;
            }
            if (message_code != MESSAGE_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 (message_code)
            {
                case MESSAGE_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 MESSAGE_KEY_DOWN:
                    event_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)
                                                    LBEqual(&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)
                                                    LBEqual(&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)
                                                    LBEqual(&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)
                                                    LBEqual(&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)
                                            {
                                                QueueRemove(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", 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 ZealC programs completely.\n");
                                Free(filename);
                                filename = NULL;
                                JukeBox(dirname, &filename);
                                if (filename)
                                {
                                    psm.screen_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.screen_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 =counts.jiffies;
                                        if (mouse.pos_text.y - Fs->win_top < 18)
                                            message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN |
                                                            1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_MS_R_UP | 1 << MESSAGE_CMD);
                                        else
                                            message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN |
                                                            1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_CMD);
                                        if (message_code)
                                        {
                                            Sound;
                                            psm.playing = FALSE;
                                            was_playing = TRUE;
                                            if (mouse.pos_text.y - Fs->win_top >= 18 && message_code == MESSAGE_MS_L_DOWN)
                                                goto mo_start;
                                            else
                                                goto mo_got_message;
                                        }
                                        psm.cur_note = tmpn;
                                        psm.screen_x += tmpn->x - 0.33 * GR_WIDTH;
                                        if (PsmHasWords(tmpn->word))
                                            "%s", tmpn->word;
                                        Sound(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);
                                        Sound;
                                        SleepUntil(timeout_val2);
                                    }
                                    tmpn = tmpn->next;
                                }
                                psm.cur_note = &psm.head;
                                psm.screen_x += psm.cur_note->x - GR_WIDTH / 2;
                                psm.playing = FALSE;
                                PsmMenu(psm_octave);
                                col = 0;
                                Sound;
                                break;

                            case CH_BACKSPACE:
                                tmpn=psm.cur_note->last;
                                if (tmpn != &psm.head)
                                {
                                    QueueRemove(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.screen_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 MESSAGE_KEY_UP:
                    event_time = tS;
                    break;

                case MESSAGE_MS_MOVE:
                    if (arg2 > 18 * FONT_HEIGHT)
                        Fs->win_inhibit = WIG_USER_TASK_DEFAULT;
                    else
                        Fs->win_inhibit = WIG_TASK_DEFAULT -
                                            WIF_SELF_FOCUS - WIF_SELF_BORDER - WIF_FOCUS_TASK_MENU - WIF_SELF_CTRLS;
                    break;

                case MESSAGE_MS_L_DOWN:
                    PsmDownKey(arg1, arg2);
                    PsmLeftClick(arg1, arg2);
                    break;

                case MESSAGE_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 = ListSub(Ona2Note(ona), psm_note_list);
                Sound(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 = PsmConvertDuration(music.tempo * (event_time-note_down_time));
                        PsmSetWidth(tmpn);
                        QueueInsert(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:
        MessageGet(,, 1 << MESSAGE_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;
}