Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions static/css/daw.css
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,16 @@ input, textarea { font-family: inherit; }
border-color: rgba(255,255,255,0.18);
}

.sections-add-btn-label[aria-disabled="true"] {
opacity: 0.4;
cursor: not-allowed;
}
.sections-add-btn-label[aria-disabled="true"]:hover {
background: none;
color: var(--muted);
border-color: var(--border);
}

/* Clear-all sections. Neutral until it is armed, then it states plainly that
the next click destroys something, because nothing here can be undone. */
.sections-clear-btn[data-armed="1"] {
Expand Down
2 changes: 1 addition & 1 deletion static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ <h2 class="lyrics-title" id="lyricsTitle" data-i18n="lyrics.title">Lyrics</h2>
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true"><path d="M4 7h16M9 7V5h6v2M7 7l1 13h8l1-13"/></svg>
<span class="sections-clear-label" data-i18n="sections.clear">Clear</span>
</button>
<button class="sections-add-btn-label" id="sectionsAddBtn" type="button" title="Add section" aria-label="Add section" data-i18n-title="sections.addAria" data-i18n-aria-label="sections.addAria">
<button class="sections-add-btn-label" id="sectionsAddBtn" type="button" title="Add section" aria-label="Add section" data-i18n-aria-label="sections.addAria">
<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
<span data-i18n="sections.add">Add</span>
</button>
Expand Down
30 changes: 20 additions & 10 deletions static/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ const en = {
"help.newFolder": "Makes a folder, so you can sort your songs.",
"help.trashedSong": "A deleted song. Hold it for a second to bring it back.",
"help.song": "Click a song to open it in the studio.",
"help.addSection": "Adds a section, like a verse or a chorus, to the sections bar.",
"help.addSection": "Adds a section, like a verse or a chorus, to the sections bar. It is greyed out when there is no free space for one.",
"help.volume": "Drag to make {name} louder or quieter.",
"help.laneKeyUp": "Moves only {name} up one semitone, the smallest step in music.",
"help.laneKeyDown": "Moves only {name} down one semitone, the smallest step in music.",
Expand Down Expand Up @@ -995,6 +995,7 @@ const en = {
"sections.title": "Sections",
"sections.add": "Add",
"sections.addAria": "Add section",
"sections.addNoRoom": "No free space for a new section",
"sections.savingAria": "Saving sections",

"mixer.title": "Mixer",
Expand Down Expand Up @@ -2660,7 +2661,7 @@ const pl = {
"help.newFolder": "Tworzy folder, w którym możesz porządkować utwory.",
"help.trashedSong": "Usunięty utwór. Przytrzymaj go przez sekundę, aby go przywrócić.",
"help.song": "Kliknij utwór, aby otworzyć go w studiu.",
"help.addSection": "Dodaje sekcję, na przykład zwrotkę lub refren, do paska sekcji.",
"help.addSection": "Dodaje sekcję, na przykład zwrotkę lub refren, do paska sekcji. Jest wyszarzony, gdy nie ma na nią wolnego miejsca.",
"help.volume": "Przeciągnij, aby {name} grało głośniej lub ciszej.",
"help.laneKeyUp": "Podnosi tylko {name} o półton, najmniejszy krok w muzyce.",
"help.laneKeyDown": "Obniża tylko {name} o półton, najmniejszy krok w muzyce.",
Expand Down Expand Up @@ -2759,6 +2760,7 @@ const pl = {
"sections.title": "Sekcje",
"sections.add": "Dodaj",
"sections.addAria": "Dodaj sekcję",
"sections.addNoRoom": "Brak wolnego miejsca na nową sekcję",
"sections.savingAria": "Zapisywanie sekcji",

"mixer.title": "Mikser",
Expand Down Expand Up @@ -4414,7 +4416,7 @@ const ja = {
"help.newFolder": "曲を整理するためのフォルダーを作ります。",
"help.trashedSong": "削除した曲です。1 秒長押しすると元に戻ります。",
"help.song": "曲をクリックするとスタジオで開きます。",
"help.addSection": "Aメロやサビなどのセクションをセクションバーに追加します。",
"help.addSection": "Aメロやサビなどのセクションをセクションバーに追加します。 空きがないときはグレーになります。",
"help.volume": "ドラッグして{name}の音量を上げ下げします。",
"help.laneKeyUp": "{name}だけを半音上げます。半音は音楽で最も小さな音程の単位です。",
"help.laneKeyDown": "{name}だけを半音下げます。半音は音楽で最も小さな音程の単位です。",
Expand Down Expand Up @@ -4513,6 +4515,7 @@ const ja = {
"sections.title": "セクション",
"sections.add": "追加",
"sections.addAria": "セクションを追加",
"sections.addNoRoom": "新しいセクションを置く空きがありません",
"sections.savingAria": "セクションを保存中",

"mixer.title": "ミキサー",
Expand Down Expand Up @@ -6143,7 +6146,7 @@ const zhHans = {
"help.newFolder": "新建文件夹,用来整理歌曲。",
"help.trashedSong": "已删除的歌曲。按住一秒即可恢复。",
"help.song": "点击歌曲,在工作室中打开它。",
"help.addSection": "在段落栏中添加一个段落,例如主歌或副歌。",
"help.addSection": "在段落栏中添加一个段落,例如主歌或副歌。 没有空间时按钮会变灰。",
"help.volume": "拖动以调大或调小{name}的音量。",
"help.laneKeyUp": "只把{name}升高一个半音,半音是音乐中最小的音程。",
"help.laneKeyDown": "只把{name}降低一个半音,半音是音乐中最小的音程。",
Expand Down Expand Up @@ -6242,6 +6245,7 @@ const zhHans = {
"sections.title": "段落",
"sections.add": "添加",
"sections.addAria": "添加段落",
"sections.addNoRoom": "没有空间添加新段落",
"sections.savingAria": "正在保存段落",

"mixer.title": "混音台",
Expand Down Expand Up @@ -7872,7 +7876,7 @@ const de = {
"help.newFolder": "Legt einen Ordner an, damit du deine Songs sortieren kannst.",
"help.trashedSong": "Ein gelöschter Song. Halte ihn eine Sekunde lang gedrückt, um ihn zurückzuholen.",
"help.song": "Klick auf einen Song, um ihn im Studio zu öffnen.",
"help.addSection": "Fügt der Abschnittsleiste einen Abschnitt hinzu, etwa eine Strophe oder einen Refrain.",
"help.addSection": "Fügt der Abschnittsleiste einen Abschnitt hinzu, etwa eine Strophe oder einen Refrain. Er ist ausgegraut, wenn kein Platz frei ist.",
"help.volume": "Zieh, um {name} lauter oder leiser zu machen.",
"help.laneKeyUp": "Hebt nur {name} um einen Halbton an, den kleinsten Schritt in der Musik.",
"help.laneKeyDown": "Senkt nur {name} um einen Halbton, den kleinsten Schritt in der Musik.",
Expand Down Expand Up @@ -7971,6 +7975,7 @@ const de = {
"sections.title": "Abschnitte",
"sections.add": "Hinzufügen",
"sections.addAria": "Abschnitt hinzufügen",
"sections.addNoRoom": "Kein freier Platz für einen neuen Abschnitt",
"sections.savingAria": "Abschnitte werden gespeichert",

"mixer.title": "Mixer",
Expand Down Expand Up @@ -9612,7 +9617,7 @@ const pt = {
"help.newFolder": "Cria uma pasta para organizar suas músicas.",
"help.trashedSong": "Uma música apagada. Segure-a por um segundo para trazê-la de volta.",
"help.song": "Clique em uma música para abri-la no estúdio.",
"help.addSection": "Adiciona uma seção, como um verso ou um refrão, à barra de seções.",
"help.addSection": "Adiciona uma seção, como um verso ou um refrão, à barra de seções. Fica cinza quando não há espaço livre.",
"help.volume": "Arraste para deixar {name} mais alto ou mais baixo.",
"help.laneKeyUp": "Sobe só {name} um semitom, o menor passo na música.",
"help.laneKeyDown": "Desce só {name} um semitom, o menor passo na música.",
Expand Down Expand Up @@ -9711,6 +9716,7 @@ const pt = {
"sections.title": "Seções",
"sections.add": "Adicionar",
"sections.addAria": "Adicionar seção",
"sections.addNoRoom": "Sem espaço livre para uma nova seção",
"sections.savingAria": "Salvando seções",

"mixer.title": "Mixer",
Expand Down Expand Up @@ -11354,7 +11360,7 @@ const id = {
"help.newFolder": "Membuat folder untuk mengatur lagu Anda.",
"help.trashedSong": "Lagu yang dihapus. Tahan selama satu detik untuk mengembalikannya.",
"help.song": "Klik lagu untuk membukanya di studio.",
"help.addSection": "Menambahkan bagian, seperti bait atau reff, ke bilah bagian.",
"help.addSection": "Menambahkan bagian, seperti bait atau reff, ke bilah bagian. Tombol ini redup jika tidak ada ruang kosong.",
"help.volume": "Geser untuk membuat {name} lebih keras atau lebih pelan.",
"help.laneKeyUp": "Menaikkan hanya {name} satu semitone, langkah terkecil dalam musik.",
"help.laneKeyDown": "Menurunkan hanya {name} satu semitone, langkah terkecil dalam musik.",
Expand Down Expand Up @@ -11453,6 +11459,7 @@ const id = {
"sections.title": "Bagian",
"sections.add": "Tambah",
"sections.addAria": "Tambah bagian",
"sections.addNoRoom": "Tidak ada ruang kosong untuk bagian baru",
"sections.savingAria": "Menyimpan bagian",

"mixer.title": "Mixer",
Expand Down Expand Up @@ -13083,7 +13090,7 @@ const fr = {
"help.newFolder": "Crée un dossier pour ranger vos morceaux.",
"help.trashedSong": "Un morceau supprimé. Maintenez-le une seconde pour le récupérer.",
"help.song": "Cliquez sur un morceau pour l'ouvrir dans le studio.",
"help.addSection": "Ajoute une section, comme un couplet ou un refrain, à la barre des sections.",
"help.addSection": "Ajoute une section, comme un couplet ou un refrain, à la barre des sections. Il est grisé quand il n'y a pas de place libre.",
"help.volume": "Faites glisser pour rendre {name} plus fort ou plus faible.",
"help.laneKeyUp": "Monte seulement {name} d'un demi-ton, le plus petit pas en musique.",
"help.laneKeyDown": "Descend seulement {name} d'un demi-ton, le plus petit pas en musique.",
Expand Down Expand Up @@ -13182,6 +13189,7 @@ const fr = {
"sections.title": "Sections",
"sections.add": "Ajouter",
"sections.addAria": "Ajouter une section",
"sections.addNoRoom": "Pas de place libre pour une nouvelle section",
"sections.savingAria": "Enregistrement des sections",

"mixer.title": "Mixage",
Expand Down Expand Up @@ -15224,7 +15232,7 @@ const es = {
"help.newFolder": "Crea una carpeta para ordenar tus canciones.",
"help.trashedSong": "Una canción borrada. Mantenla pulsada un segundo para recuperarla.",
"help.song": "Haz clic en una canción para abrirla en el estudio.",
"help.addSection": "Añade una sección, como una estrofa o un estribillo, a la barra de secciones.",
"help.addSection": "Añade una sección, como una estrofa o un estribillo, a la barra de secciones. Aparece en gris cuando no hay espacio libre.",
"help.volume": "Arrastra para subir o bajar el volumen de {name}.",
"help.laneKeyUp": "Sube solo {name} un semitono, el paso más pequeño en música.",
"help.laneKeyDown": "Baja solo {name} un semitono, el paso más pequeño en música.",
Expand Down Expand Up @@ -15323,6 +15331,7 @@ const es = {
"sections.title": "Secciones",
"sections.add": "Añadir",
"sections.addAria": "Añadir sección",
"sections.addNoRoom": "No hay espacio libre para una nueva sección",
"sections.savingAria": "Guardando secciones",

"mixer.title": "Mezclador",
Expand Down Expand Up @@ -16987,7 +16996,7 @@ const ko = {
"help.newFolder": "곡을 정리할 폴더를 만들어요.",
"help.trashedSong": "삭제한 곡이에요. 1초 동안 누르고 있으면 되살아나요.",
"help.song": "곡을 클릭하면 스튜디오에서 열려요.",
"help.addSection": "벌스나 코러스 같은 구간을 섹션 막대에 추가해요.",
"help.addSection": "벌스나 코러스 같은 구간을 섹션 막대에 추가해요. 빈 공간이 없으면 회색으로 바뀌어요.",
"help.volume": "끌어서 {name} 소리를 키우거나 줄여요.",
"help.laneKeyUp": "{name}만 반음 올려요. 반음은 음악에서 가장 작은 음 간격이에요.",
"help.laneKeyDown": "{name}만 반음 내려요. 반음은 음악에서 가장 작은 음 간격이에요.",
Expand Down Expand Up @@ -17086,6 +17095,7 @@ const ko = {
"sections.title": "구간",
"sections.add": "추가",
"sections.addAria": "구간 추가",
"sections.addNoRoom": "새 구간을 넣을 빈 공간이 없어요",
"sections.savingAria": "구간 저장 중",

"mixer.title": "믹서",
Expand Down
5 changes: 3 additions & 2 deletions static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
playBtn, loopBtn, totalDuration, loopEnabled, loopStart, loopEnd,
setLoopStart, setLoopEnd, selectedStems, saveSelectedStems, stemSelectionReady,
currentJobId, vocalSplitMode, vocalSplitModeReady, setVocalSplitMode,
setAutoSectionsResetFn,
setAutoSectionsResetFn, onLoopChange,
} from "./state.js";
import { STEM_NAMES, syncStemNamesFromAPI } from "./constants.js";
import { refreshStemChoiceVisuals } from "./stemChoice.js";
Expand All @@ -14,7 +14,7 @@ import { initSearch } from "./search.js";
import { wireTransportButtons } from "./transport.js";
import { wireBeatGridUi } from "./beatgridUi.js";
import { togglePlayPause, updateLoopRegionVisual, toggleMetronome, transport, setPlayheadTime, setLoopRange } from "./transport.js";
import { setSectionsLoopBridge } from "./sections.js";
import { setSectionsLoopBridge, refreshSectionsAddState } from "./sections.js";
import { wireStemListControls, wireMixerToolbar, laneDragIcon } from "./mixer.js";
import { initCatalog, collectDiagnostics } from "./catalog.js";
import { initNotifications, notifyFailure, dismissFailuresByJobId } from "./notifications.js";
Expand Down Expand Up @@ -485,6 +485,7 @@ setSectionsLoopBridge({
getLoop: () => ({ enabled: loopEnabled, start: loopStart, end: loopEnd }),
setLoopRange,
});
onLoopChange(refreshSectionsAddState);
wireBeatGridUi();
wireFooterControls();
requestAnimationFrame(drawFooterPlaceholder);
Expand Down
20 changes: 19 additions & 1 deletion static/js/sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ export function initSections(trackId, sections, duration) {
const addBtn = document.getElementById("sectionsAddBtn");
if (addBtn && !addBtn.dataset.sectionsWired) {
addBtn.dataset.sectionsWired = "1";
addBtn.addEventListener("click", () => _addSection());
addBtn.addEventListener("click", () => {
// aria-disabled, not disabled, so the tooltip still says why.
if (addBtn.getAttribute("aria-disabled") === "true") return;
_addSection();
});
}
_wireClearButton();

Expand Down Expand Up @@ -97,6 +101,7 @@ export function destroySections() {
if (_container) _container.innerHTML = "";
_container = null;
_refreshClearVisibility();
refreshSectionsAddState();
}

// ─── Rendering ────────────────────────────────────────────
Expand All @@ -105,6 +110,7 @@ function _render() {
// Before the container guard: the button lives in the header, not the
// ribbon, so its state must stay correct even when the ribbon is absent.
_refreshClearVisibility();
refreshSectionsAddState();
if (!_container) return;
_container.innerHTML = "";

Expand Down Expand Up @@ -432,6 +438,18 @@ function _newSectionBounds() {
return { start, end };
}

// Add is unavailable exactly when pressing it would do nothing: no place for a
// section (#775) and no armed loop that would at least explain itself with the
// "loop overlaps" notice. Recomputed whenever sections, the song, the loop or
// the language change.
export function refreshSectionsAddState() {
const btn = document.getElementById("sectionsAddBtn");
if (!btn) return;
const blocked = !_armedLoop() && _newSectionBounds() === null;
btn.setAttribute("aria-disabled", String(blocked));
btn.title = blocked ? t("sections.addNoRoom") : t("sections.addAria");
}

function _addSection() {
const bounds = _newSectionBounds();
if (!bounds) {
Expand Down
11 changes: 8 additions & 3 deletions static/js/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,14 @@ export function setCurrentJobId(v) { currentJobId = v; }
export function setForegroundJobId(v) { foregroundJobId = v; }
export function setTrackIndex(v) { trackIndex = v; }
export function setTotalDuration(v) { totalDuration = v; }
export function setLoopEnabled(v) { loopEnabled = v; }
export function setLoopStart(v) { loopStart = v; }
export function setLoopEnd(v) { loopEnd = v; }
// Whoever needs to react to the loop changing (the Sections Add button depends
// on whether a loop is armed) subscribes here rather than polling.
const _loopListeners = [];
export function onLoopChange(fn) { _loopListeners.push(fn); }
function _loopChanged() { for (const fn of _loopListeners) fn(); }
export function setLoopEnabled(v) { loopEnabled = v; _loopChanged(); }
export function setLoopStart(v) { loopStart = v; _loopChanged(); }
export function setLoopEnd(v) { loopEnd = v; _loopChanged(); }
export function setAudioContext(v) { audioContext = v; }
export function setMasterVolume(v) { masterVolume = v; }
export let playbackSpeed = 1.0;
Expand Down
Loading
Loading