fix: few minor UI and behavior adjustments
This commit is contained in:
@@ -905,6 +905,19 @@ const SeparatorTab: React.FC<{ mode: KGOneMode }> = ({ mode }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Reset separator results whenever the active project changes
|
||||||
|
useEffect(() => {
|
||||||
|
stemAudioUrls.forEach(s => URL.revokeObjectURL(s.url));
|
||||||
|
setStemAudioUrls([]);
|
||||||
|
setGenStatus('idle');
|
||||||
|
setGenHint('');
|
||||||
|
setErrorMsg('');
|
||||||
|
setIsImporting(false);
|
||||||
|
setImportError('');
|
||||||
|
originalRegionRef.current = null;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [projectName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setModel(availableSeparatorModels[0].value);
|
setModel(availableSeparatorModels[0].value);
|
||||||
}, [availableSeparatorModels]);
|
}, [availableSeparatorModels]);
|
||||||
@@ -1363,7 +1376,8 @@ const SeparatorTab: React.FC<{ mode: KGOneMode }> = ({ mode }) => {
|
|||||||
<div className="kgone-local-mode-text">
|
<div className="kgone-local-mode-text">
|
||||||
Only Vocal and Instrument (Medium Accuracy) is available while not integrated with K.G.One Music Studio server.
|
Only Vocal and Instrument (Medium Accuracy) is available while not integrated with K.G.One Music Studio server.
|
||||||
Processing in local may take long time depending on your hardware. When fallback to CPU happens, the webpage may
|
Processing in local may take long time depending on your hardware. When fallback to CPU happens, the webpage may
|
||||||
temporarily hang with little or no UI response until processing advances.
|
temporarily hang with little or no UI response until processing advances.{' '}
|
||||||
|
<a href="https://github.com/KGAudioLab/K.G.One" target="_blank" rel="noopener noreferrer">Learn more about K.G.One Music Studio server integration.</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="kgone-runtime-row">
|
<div className="kgone-runtime-row">
|
||||||
<div className="kgone-provider-chip">Provider: {localProviderLabel}</div>
|
<div className="kgone-provider-chip">Provider: {localProviderLabel}</div>
|
||||||
@@ -2021,7 +2035,7 @@ const RemixTab: React.FC = () => {
|
|||||||
|
|
||||||
const RepaintTab: React.FC = () => {
|
const RepaintTab: React.FC = () => {
|
||||||
const { selectedRegionIds, projectName, bpm, timeSignature, maxBars,
|
const { selectedRegionIds, projectName, bpm, timeSignature, maxBars,
|
||||||
isLooping, loopingRange, refreshProjectState } = useProjectStore();
|
isLooping, loopingRange, refreshProjectState } = useProjectStore();
|
||||||
|
|
||||||
// Form state (mirrors FullSongTab)
|
// Form state (mirrors FullSongTab)
|
||||||
const [caption, setCaption] = useState('');
|
const [caption, setCaption] = useState('');
|
||||||
@@ -2573,24 +2587,37 @@ const KGOnePanel: React.FC<KGOnePanelProps> = ({ isVisible }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={`kgone-panel${isVisible ? '' : ' is-hidden'}`}>
|
<div className={`kgone-panel${isVisible ? '' : ' is-hidden'}`}>
|
||||||
<div className="kgone-panel-header">
|
<div className="kgone-panel-header">
|
||||||
<h3>K.G.One Music Generator</h3>
|
<h3>{mode === 'local-separator' ? 'Music Generator' : 'K.G.One Music Generator'}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="kgone-tabs">
|
<div className="kgone-tabs">
|
||||||
{/* Clip tab temporarily disabled, will enable in the future */}
|
{/* Clip tab temporarily disabled, will enable in the future */}
|
||||||
{KGONE_TABS.map(tab => (
|
{KGONE_TABS.map(tab => {
|
||||||
<button
|
const isDisabled = disabledTabs.has(tab);
|
||||||
key={tab}
|
const button = (
|
||||||
className={`kgone-tab${activeTab === tab ? ' active' : ''}${disabledTabs.has(tab) ? ' is-disabled' : ''}`}
|
<button
|
||||||
onClick={() => {
|
key={tab}
|
||||||
if (disabledTabs.has(tab)) return;
|
className={`kgone-tab${activeTab === tab ? ' active' : ''}${isDisabled ? ' is-disabled' : ''}`}
|
||||||
setActiveTab(tab);
|
onClick={() => {
|
||||||
}}
|
if (isDisabled) return;
|
||||||
disabled={disabledTabs.has(tab)}
|
setActiveTab(tab);
|
||||||
>
|
}}
|
||||||
{formatKGOneTabLabel(tab)}
|
disabled={isDisabled}
|
||||||
</button>
|
style={isDisabled ? { width: '100%' } : undefined}
|
||||||
))}
|
>
|
||||||
|
{formatKGOneTabLabel(tab)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
return isDisabled ? (
|
||||||
|
<span
|
||||||
|
key={tab}
|
||||||
|
title="Requires K.G.One Music Studio server integration"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
{button}
|
||||||
|
</span>
|
||||||
|
) : button;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="kgone-panel-body">
|
<div className="kgone-panel-body">
|
||||||
|
|||||||
@@ -122,6 +122,30 @@ const Toolbar: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Untitled Project is ephemeral — no existing save to preserve, so rename directly
|
||||||
|
if (savedProjectName === RESERVED_PROJECT_NAME) {
|
||||||
|
const storage = KGProjectStorage.getInstance();
|
||||||
|
const exists = await storage.exists(newName);
|
||||||
|
if (exists) {
|
||||||
|
const confirmed = await showConfirm(
|
||||||
|
`Project "${newName}" already exists. Do you want to overwrite it?`
|
||||||
|
);
|
||||||
|
if (!confirmed) return;
|
||||||
|
setProjectName(newName);
|
||||||
|
await saveProject(newName, savedProjectName, setStatus, (finalName) => {
|
||||||
|
setSavedProjectName(finalName);
|
||||||
|
if (finalName !== newName) setProjectName(finalName);
|
||||||
|
}, true /* forceOverwrite */);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setProjectName(newName);
|
||||||
|
await saveProject(newName, savedProjectName, setStatus, (finalName) => {
|
||||||
|
setSavedProjectName(finalName);
|
||||||
|
if (finalName !== newName) setProjectName(finalName);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Ask whether the user wants to rename or save as a copy
|
// Ask whether the user wants to rename or save as a copy
|
||||||
const choice = await showChoice(
|
const choice = await showChoice(
|
||||||
"Would you like to rename this project, or save it as a new copy?",
|
"Would you like to rename this project, or save it as a new copy?",
|
||||||
|
|||||||
@@ -591,7 +591,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
disabled={kgoneServerManaged}
|
disabled={kgoneServerManaged}
|
||||||
/>
|
/>
|
||||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||||
Base URL of a running K.G.One server. Used for full-song generation, clip generation, and stem separation.
|
Base URL of a running K.G.One Music Studio server. Used for full-song generation, clip generation, and stem separation.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ interface RegionItemProps {
|
|||||||
audioBuffer?: AudioBuffer;
|
audioBuffer?: AudioBuffer;
|
||||||
previewWaveformPeaks?: AudioRecordingPeak[];
|
previewWaveformPeaks?: AudioRecordingPeak[];
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
|
isAudioRegion?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RegionItem: React.FC<RegionItemProps> = ({
|
const RegionItem: React.FC<RegionItemProps> = ({
|
||||||
@@ -71,6 +72,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
audioBuffer,
|
audioBuffer,
|
||||||
previewWaveformPeaks,
|
previewWaveformPeaks,
|
||||||
isPreview = false,
|
isPreview = false,
|
||||||
|
isAudioRegion = false,
|
||||||
}) => {
|
}) => {
|
||||||
// Get selection state and time signature from store
|
// Get selection state and time signature from store
|
||||||
const { selectedRegionIds, timeSignature, bpm } = useProjectStore();
|
const { selectedRegionIds, timeSignature, bpm } = useProjectStore();
|
||||||
@@ -652,7 +654,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
className={`track-region ${isDragging ? 'dragging' : ''} ${isSelected ? (isPrimarySelected ? 'selected' : 'selected-secondary') : ''} ${audioRegion ? 'audio-region' : ''}`}
|
className={`track-region ${isDragging ? 'dragging' : ''} ${isSelected ? (isPrimarySelected ? 'selected' : 'selected-secondary') : ''} ${(audioRegion || isAudioRegion) ? 'audio-region' : ''}`}
|
||||||
style={{ ...style, cursor: isPreview ? 'default' : cursor, ...(isFineDragging ? { transform: `translateX(${fineTranslateX}px)`, zIndex: 100 } : {}) }}
|
style={{ ...style, cursor: isPreview ? 'default' : cursor, ...(isFineDragging ? { transform: `translateX(${fineTranslateX}px)`, zIndex: 100 } : {}) }}
|
||||||
onMouseMove={isPreview ? undefined : handleMouseMove}
|
onMouseMove={isPreview ? undefined : handleMouseMove}
|
||||||
onMouseLeave={isPreview ? undefined : handleMouseLeave}
|
onMouseLeave={isPreview ? undefined : handleMouseLeave}
|
||||||
@@ -666,7 +668,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
<div className="region-header">
|
<div className="region-header">
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div className={`region-content${audioRegion ? ' audio-region-content' : ''}`} ref={regionContentRef}>
|
<div className={`region-content${(audioRegion || isAudioRegion) ? ' audio-region-content' : ''}`} ref={regionContentRef}>
|
||||||
{!isPreview && <div className="region-left-buttons">
|
{!isPreview && <div className="region-left-buttons">
|
||||||
{!audioRegion && (
|
{!audioRegion && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -654,6 +654,7 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
|
|||||||
trackIndex={index}
|
trackIndex={index}
|
||||||
previewWaveformPeaks={recordingAudioPreviewPeaks}
|
previewWaveformPeaks={recordingAudioPreviewPeaks}
|
||||||
isPreview
|
isPreview
|
||||||
|
isAudioRegion
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAutomationActive && activeTrackAutomationType && (
|
{isAutomationActive && activeTrackAutomationType && (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { KGMidiTrack } from './track/KGMidiTrack';
|
|||||||
import { KGAudioTrack } from './track/KGAudioTrack';
|
import { KGAudioTrack } from './track/KGAudioTrack';
|
||||||
import { type TimeSignature, WithDefault } from '../types/projectTypes';
|
import { type TimeSignature, WithDefault } from '../types/projectTypes';
|
||||||
import { TIME_CONSTANTS, KEY_SIGNATURE_MAP } from '../constants/coreConstants';
|
import { TIME_CONSTANTS, KEY_SIGNATURE_MAP } from '../constants/coreConstants';
|
||||||
|
import { RESERVED_PROJECT_NAME } from '../util/projectNameUtil';
|
||||||
|
|
||||||
// Type for valid key signatures
|
// Type for valid key signatures
|
||||||
export type KeySignature = keyof typeof KEY_SIGNATURE_MAP;
|
export type KeySignature = keyof typeof KEY_SIGNATURE_MAP;
|
||||||
@@ -14,7 +15,7 @@ export type KeySignature = keyof typeof KEY_SIGNATURE_MAP;
|
|||||||
*/
|
*/
|
||||||
export class KGProject {
|
export class KGProject {
|
||||||
@Expose()
|
@Expose()
|
||||||
private name: string = "Untitled Project";
|
private name: string = RESERVED_PROJECT_NAME;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
private maxBars: number = 32;
|
private maxBars: number = 32;
|
||||||
@@ -68,7 +69,7 @@ export class KGProject {
|
|||||||
private tracks: KGTrack[] = [];
|
private tracks: KGTrack[] = [];
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
constructor(name: string = "Untitled Project", maxBars: number = 32, currentBars: number = 0, bpm: number = 125, timeSignature: TimeSignature = { numerator: 4, denominator: 4 }, keySignature: KeySignature = "C major", selectedMode: string = "ionian", isLooping: boolean = false, loopingRange: [number, number] = [0, 0], barWidthMultiplier: number = 1, tracks: KGTrack[] = [], projectStructureVersion: number = KGProject.CURRENT_PROJECT_STRUCTURE_VERSION) {
|
constructor(name: string = RESERVED_PROJECT_NAME, maxBars: number = 32, currentBars: number = 0, bpm: number = 125, timeSignature: TimeSignature = { numerator: 4, denominator: 4 }, keySignature: KeySignature = "C major", selectedMode: string = "ionian", isLooping: boolean = false, loopingRange: [number, number] = [0, 0], barWidthMultiplier: number = 1, tracks: KGTrack[] = [], projectStructureVersion: number = KGProject.CURRENT_PROJECT_STRUCTURE_VERSION) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.maxBars = maxBars;
|
this.maxBars = maxBars;
|
||||||
this.currentBars = currentBars;
|
this.currentBars = currentBars;
|
||||||
|
|||||||
Reference in New Issue
Block a user