U0 PsmNoteDel(PsmNote *tmpn)
{
        Free(tmpn->word);
        Free(tmpn);
}

PsmNote *PsmNoteCopy(PsmNote *tmpn)
{
        PsmNote *tmpn1 = MAllocIdent(tmpn);

        if (tmpn->word)
                tmpn1->word = StrNew(tmpn->word);
        else
                tmpn1->word = NULL;

        return tmpn1;
}

U0 PsmSongDel(PsmNote *head)
{
        PsmNote *tmpn, *tmpn1;

        tmpn = head->next;
        while (tmpn != head)
        {
                tmpn1 = tmpn->next;
                PsmNoteDel(tmpn);
                tmpn = tmpn1;
        }
        QueueInit(head);
}

U0 PsmCutToClip()
{
        PsmNote *tmpn, *tmpn1;

        PsmSongDel(&psm.clip);
        tmpn = psm.head.next;
        while (tmpn != &psm.head)
        {
                tmpn1 = tmpn->next;
                if (tmpn->flags & PSMF_SEL)
                {
                        if (psm.cur_note == tmpn)
                                psm.cur_note = tmpn->next;
                        QueueRemove(tmpn);
                        tmpn->flags &= ~PSMF_SEL;
                        QueueInsert(tmpn, psm.clip.last);
                }
                tmpn = tmpn1;
        }
}

U0 PsmPasteClip()
{
        PsmNote *tmpn, *tmpn1;

        tmpn = psm.clip.next;
        while (tmpn != &psm.clip)
        {
                tmpn1 = PsmNoteCopy(tmpn);
                QueueInsert(tmpn1, psm.cur_note->last);
                tmpn = tmpn->next;
        }
}

U0 PsmCopyToClip()
{
        PsmNote *tmpn, *tmpn1;

        PsmSongDel(&psm.clip);
        tmpn = psm.head.next;
        while (tmpn != &psm.head)
        {
                if (tmpn->flags & PSMF_SEL)
                {
                        tmpn->flags &= ~PSMF_SEL;
                        tmpn1 = PsmNoteCopy(tmpn);
                        QueueInsert(tmpn1, psm.clip.last);
                }
                tmpn = tmpn->next;
        }
}

PsmNote *PsmFindNote(I64 x, I64)
{
        PsmNote *tmpn=psm.head.next;

        PsmRecalcNoteXY;
        x += PSM_NOTE_SPACING / 2;
        while (x > tmpn->next->x && tmpn != &psm.head)
                tmpn = tmpn->next;

        return tmpn;
}

U8 *PsmMusicSetOctave(U8 *st, I64 *psm_octave)
{
        while ('0' <= *st <= '9')
                *psm_octave = *st++ - '0';

        return st;
}

U8 *PsmMusicSetNoteLen(U8 *st, F64 *psm_duration)
{
        Bool cont = TRUE;

        do
        {
                switch (*st++)
                {
                        case 'w': *psm_duration = 4.0;  break;
                        case 'h': *psm_duration = 2.0;  break;
                        case 'q': *psm_duration = 1.0;  break;
                        case 'e': *psm_duration = 0.5;  break;
                        case 's': *psm_duration = 0.25; break;
                        case 't': *psm_duration = 2.0 * *psm_duration / 3.0;    break;
                        case '.': *psm_duration = 1.5 * *psm_duration;                  break;

                        default:
                                st--;
                                cont = FALSE;
                }
        }
        while (cont);

        return st;
}

U0 PsmLoadSongStr(U8 *st, I64 *psm_octave, F64 *psm_duration)
{
        PsmNote *tmpn, *tmpn1;
        I64              note, i = 0;

        while (*st)
        {
                tmpn = CAlloc(sizeof(PsmNote));
                while (*st && !('A' <= *st <= 'G') && *st != 'R')
                {
                        if (*st == 'M')
                        {
                                tmpn1 = CAlloc(sizeof(PsmNote));
                                tmpn1->type = PSMT_METER;
                                st++;
                                if ('1' <= *st <= '9')
                                        tmpn1->meter_top = *st++ - '0';
                                else
                                        tmpn1->meter_top = 4;
                                if (*st == '/')
                                        st++;
                                if ('1' <= *st <= '9')
                                        tmpn1->meter_bottom = *st++ - '0';
                                else
                                        tmpn1->meter_bottom = 4;
                                PsmSetWidth(tmpn1);
                                QueueInsert(tmpn1, psm.head.last);
                        }
                        while (*st == '(')
                        {
                                Bts(&tmpn->flags, PSMf_TIE);
                                st++;
                        }
                        st = PsmMusicSetOctave(st, psm_octave);
                        st = PsmMusicSetNoteLen(st, psm_duration);
                }
                if (!*st)
                {
                        PsmNoteDel(tmpn);
                        break;
                }
                note = *st++ - 'A';
                if (note < 7)
                {
                        note = music.note_map[note];
                        if (*st == 'b')
                        {
                                Bts(&tmpn->flags, PSMf_FLAT);
                                note--;
                                st++;
                                if (note < 0) //Ab
                                        note = 11;
                                else if (note == 2) //Cb
                                        *psm_octave -= 1;
                        }
                        else if (*st == '#')
                        {
                                Bts(&tmpn->flags, PSMf_SHARP);
                                note++;
                                st++;
                                if (note > 11) //G#
                                        note = 0;
                                else if (note == 3) //B#
                                        *psm_octave += 1;
                        }
                        tmpn->ona = Note2Ona(note, *psm_octave);
                }
                else
                        tmpn->ona = 0;
                if (*psm_duration <= 2*.25 / 3)
                        i = 0;
                else if (*psm_duration <= .25)
                        i = 1;
                else if (*psm_duration <= 2 * .5 / 3)
                        i = 2;
                else if (*psm_duration <= .5)
                        i = 3;
                else if (*psm_duration <= 2.0 / 3)
                        i = 4;
                else if (*psm_duration <= .5 * 1.5)
                        i = 5;
                else if (*psm_duration <= 1.0)
                        i = 6;
                else if (*psm_duration <= 1.5)
                        i = 7;
                else if (*psm_duration <= 2.0)
                        i = 8;
                else if (*psm_duration <= 3.0)
                        i = 9;
                else if (*psm_duration <= 4.0)
                        i = 10;
                else
                        i = 11;
                tmpn->duration = i;
                tmpn->type = PSMT_NOTE;
                PsmSetWidth(tmpn);
                QueueInsert(tmpn, psm.cur_note->last);
        }
}

U0 PsmLoadSong(U8 *filename, I64 *psm_octave, F64 *psm_duration)
{
        U8                      *st;
        PsmNote         *tmpn;
        CCompCtrl       *cc = CompCtrlNew(MStrPrint("#include \"%s\"", filename));

        if (FileOcc("incomplete", filename, ""))
                psm.incomplete_entry->checked = TRUE;
        else
                psm.incomplete_entry->checked = FALSE;
        while (Lex(cc))
        {
                if (cc->token == TK_IDENT)
                        if (!StrCompare(cc->cur_str, "Play"))
                        {
                                if (Lex(cc) == '(')
                                        if (Lex(cc) == TK_STR)
                                        {
                                                tmpn = psm.head.last;
                                                st = LexExtStr(cc);
                                                PsmLoadSongStr(st, psm_octave, psm_duration);
                                                if (cc->token == ',')
                                                {
                                                        if (Lex(cc) == TK_STR)
                                                        {
                                                                st = LexExtStr(cc);
                                                                do
                                                                {
                                                                        do tmpn = tmpn->next;
                                                                        while (tmpn != &psm.head && tmpn->type == PSMT_METER);
                                                                        if (tmpn != &psm.head)
                                                                                tmpn->word = StrNew(st);
                                                                        st += StrLen(st) + 1;
                                                                }
                                                                while (*st);
                                                        }
                                                }
                                        }
                        }
                        else if (!StrCompare(cc->cur_str, "music") && Lex(cc) == '.' && Lex(cc) == TK_IDENT)
                        {
                                if (!StrCompare(cc->cur_str, "tempo"))
                                {
                                        if (Lex(cc) == '=' && Lex(cc) == TK_F64)
                                        {
                                                music.tempo = cc->cur_f64 - 0.0005;
                                                tempo_state.tempo = Round(TEMPO_RANGE * (music.tempo - 0.5) / 4.4);
                                        }
                                }
                                else if (!StrCompare(cc->cur_str, "stacatto_factor"))
                                {
                                        if (Lex(cc) == '=' && Lex(cc) == TK_F64)
                                        {
                                                music.stacatto_factor = cc->cur_f64 - 0.0005;
                                                tempo_state.stacatto = Round(TEMPO_RANGE * (music.stacatto_factor - 0.12) / 0.88);
                                        }
                                }
                        }
        }
        CompCtrlDel(cc);
}

U8 *PsmConvertSong()
{
        PsmNote *tmpn;
        U8              *st, *src, *dst;
        I64              i, ona, note, octave, last_octave, last_duration;

        i = 0;
        tmpn = psm.head.next;
        last_octave = I64_MIN;
        last_duration = -1;
        while (tmpn != &psm.head)
        {
                dst = &tmpn->ascii;
                if (tmpn->type == PSMT_METER)
                {
                        *dst++ = 'M';
                        *dst++ = tmpn->meter_top + '0';
                        *dst++ = '/';
                        *dst++ = tmpn->meter_bottom + '0';
                }
                else
                {
                        if (tmpn->ona)
                        {
                                ona = tmpn->ona;
                                if (Bt(&tmpn->flags, PSMf_SHARP))
                                        ona--;
                                if (Bt(&tmpn->flags, PSMf_FLAT))
                                        ona++;
                                octave = Ona2Octave(ona);
                                note = Ona2Note  (ona);
                                note = music.note_map[*ListSub(note, psm_note_list) - 'A'];
                        }
                        if (Bt(&tmpn->flags, PSMf_TIE))
                                *dst++ = '(';
                        if (octave != last_octave && tmpn->ona)
                        {
                                *dst++ = octave + '0';
                                last_octave = octave;
                        }
                        if (tmpn->duration != last_duration)
                        {
                                src = ListSub(tmpn->duration, psm_duration_list);
                                *dst++ = src[0];
                                if (src[1])
                                        *dst++ = src[1];
                                last_duration = tmpn->duration;
                        }
                        if (tmpn->ona)
                        {
                                src = ListSub(note, psm_note_list);
                                *dst++ = src[0];
                                if (src[1])
                                        *dst++ = src[1];
                                else if (Bt(&tmpn->flags, PSMf_FLAT))
                                        *dst++ = 'b';
                                else if (Bt(&tmpn->flags, PSMf_SHARP))
                                        *dst++ = '#';
                        }
                        else
                                *dst++ = 'R';
                }
                *dst++ = 0;
                i += StrLen(tmpn->ascii);
                tmpn = tmpn->next;
        }

        st = MAlloc(i + 1);
        dst = st;
        tmpn = psm.head.next;
        while (tmpn != &psm.head)
        {
                StrCopy(dst, tmpn->ascii);
                dst += StrLen(tmpn->ascii);
                tmpn = tmpn->next;
        }
        *dst++ = 0;

        return st;
}

U8 *PsmSaveSong(U8 *dirname, U8 *full_filename)
{
        CDoc    *doc = DocNew(full_filename);
        Bool     has_words;
        PsmNote *tmpn, *tmpn1;
        F64              measure_len = 4, two_measure_left = 2 * measure_len;
        I64              ch;
        U8              *ptr;

        Free(PsmConvertSong); //set tmpn->ascii;

        music.tempo                             = 4.4  * tempo_state.tempo        / TEMPO_RANGE + 0.5;
        music.stacatto_factor   = 0.88 * tempo_state.stacatto / TEMPO_RANGE + 0.12;

        has_words = FALSE;
        tmpn = psm.head.next;
        while (tmpn != &psm.head)
        {
                if (PsmHasWords(tmpn->word))
                        has_words = TRUE;
                tmpn = tmpn->next;
        }
        if (psm.incomplete_entry->checked)
                DocPrint(doc, "//0 incomplete\n");
        else if (has_words)
                DocPrint(doc, "//0 has words\n");
        else
                DocPrint(doc, "//0 no nothing\n");

        DocPrint(doc, 
                                "U0 Song()\n"
                                "{\n"
                                "\tFs->task_end_cb=&SoundTaskEndCB;\n"
                                "\tMusicSettingsReset;\n"
                                "\tmusic.tempo=%6.3f;\n"
                                "\tmusic.stacatto_factor=%6.3f;\n"
                                "\ttry {\n"
                                "\t\twhile (!KeyScan) {\n"
                                "\t\t\tPlay(\"", music.tempo + 0.0005, music.stacatto_factor + 0.0005);

        tmpn = psm.head.next;
        tmpn1 = tmpn;
        has_words = FALSE;
        while (tmpn != &psm.head)
        {
                DocPrint(doc, "%s", tmpn->ascii);
                if (PsmHasWords(tmpn->word))
                        has_words = TRUE;
                if (tmpn->type == PSMT_METER)
                {
                        measure_len = tmpn->meter_top * 4.0 / tmpn->meter_bottom;
                        two_measure_left = 0;
                }
                else
                        two_measure_left -= psm_durations[tmpn->duration];
                tmpn = tmpn->next;
                if (two_measure_left < 0.001 && tmpn != &psm.head)
                {
                        if (has_words)
                        {
                                DocPrint(doc, "\",\n\t\t\"");
                                while (tmpn1 != tmpn)
                                {
                                        if (tmpn1->type != PSMT_METER)
                                        {
                                                if (ptr=tmpn1->word)
                                                {
                                                        while (ch = *ptr)
                                                                ptr++;
                                                        DocPrint(doc, "%Q\\0", tmpn1->word);
                                                }
                                                else
                                                        DocPrint(doc, " \\0");
                                        }
                                        tmpn1 = tmpn1->next;
                                }
                        }
                        DocPrint(doc,
                                                "\");\n"
                                                "\tPlay(\"");
                        two_measure_left = 2 * measure_len;
                        tmpn1 = tmpn;
                        has_words = FALSE;
                }
        }
        if (has_words)
        {
                DocPrint(doc, "\",\n\t\t\"");
                while (tmpn1 != tmpn)
                {
                        if (tmpn1->type != PSMT_METER)
                        {
                                if (ptr = tmpn1->word)
                                {
                                        while (ch = *ptr)
                                                ptr++;
                                        
                                        DocPrint(doc, "%Q\\0", tmpn1->word);
                                }
                                else
                                        DocPrint(doc, " \\0");
                        }
                        tmpn1 = tmpn1->next;
                }
        }
        DocPrint(doc,
                                "\");\n"
                                "\t\t}\n"
                                "\t} catch\n"
                                "\t\tPutExcept;\n"
                                "\tSound;\n"
                                "}\n"
                                "\n"
                                "Song;\n");
        DocRecalc(doc);
        if (full_filename)
                Free(full_filename);
        else
                StrPrint(doc->filename.name, "%s/Tmp.CC", dirname);
        DocWrite(doc, TRUE);
        full_filename = StrNew(doc->filename.name);
        DocDel(doc);

        return full_filename;
}