HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“音浪智脑“——PC端AI智能体沉浸式音乐制作工作站
文章目录

每日一句正能量
不要总是指责自己或他人为什么做不到,而是要思考怎样才能做到。
指责(无论是自责还是责他)会消耗心理能量并固化无力感;而“怎样才能做到”激发创造性思维和行动力。
前言
摘要:2026年,音乐制作进入"智能体协同创作"时代。HarmonyOS 6(API 23)引入的鸿蒙智能体框架(HMAF)将AI能力下沉至系统层,配合悬浮导航与沉浸光感特性,为PC端专业音乐制作带来了"节拍即光效、情绪即导航"的全新交互范式。本文将实战开发一款面向HarmonyOS PC的"音浪智脑"应用,展示如何利用HMAF构建"旋律生成-和声编排-混音母带-风格迁移"四层智能体协作架构,通过悬浮导航实现创作状态实时追踪,基于沉浸光感打造"BPM即氛围"的沉浸体验,以及基于多窗口架构构建浮动音轨编辑器、实时频谱面板和风格选择器的协作创作体验。
一、前言:AI音乐制作3.0时代的智能体革命
2026年,中国独立音乐人规模突破500万,但传统DAW(数字音频工作站)面临三大痛点:
- 创作瓶颈:编曲新手难以驾驭复杂的和声理论与混音技巧,一首完整作品的制作周期平均需要3个月
- 工具割裂:旋律创作、和声编排、混音母带、风格迁移分散在不同软件,工程文件兼容性差
- 反馈缺失:创作者在长时间制作中难以感知作品的情绪走向与频谱缺陷,导致成品质量参差不齐
HarmonyOS 6(API 23)的HMAF框架配合**悬浮导航(Float Navigation)与沉浸光感(Immersive Light Effects)**特性,为音乐制作带来了革命性解决方案:
- 智能体协同创作:HMAF构建的"旋律智能体"可实时生成符合情绪的旋律动机,"和声智能体"自动配器,响应延迟降至1.2秒
- BPM光效感知:根据当前工程BPM、频谱能量、情绪标签动态切换环境光色,让创作者"看见"音乐的情绪色彩
- 悬浮创作导航:底部悬浮导航实时显示四大智能体运行状态与工程进度徽章,创作者无需切换页面即可掌握全局
- PC多窗口协作:主音轨编辑器 + 浮动MIDI键盘窗口 + 浮动频谱面板 + 浮动风格选择器的四层架构,通过光效联动实现"一眼全局"
本文核心亮点:
- 四层智能体架构:旋律智能体(动机生成)、和声智能体(自动配器)、混音智能体(母带处理)、风格智能体(风格迁移)协同工作
- BPM脉搏光效:根据工程BPM(慢板蓝→行板绿→快板橙→急板红)动态渲染全屏氛围光
- 频谱能量光效:基于实时FFT频谱分析,低频触发深红光晕,高频触发冰蓝光晕
- 悬浮创作导航:底部悬浮页签承载"音轨/和声/混音/风格"四大模块,实时显示智能体状态徽章与BPM脉冲
- 多窗口光效同步:主窗口与三个浮动子窗口通过
WindowLightSync实现跨窗口光效联动,焦点感知自动调节
二、核心特性解析与技术选型
2.1 HMAF在音乐制作场景中的价值
HMAF(Harmony Intelligent Agent Framework)提供四种编排模式,在音乐制作场景中各有妙用:
| 编排模式 | 音乐制作应用场景 | 技术实现 |
|---|---|---|
| LLM模式 | 旋律智能体根据情绪标签生成旋律动机 | 基于情绪参数调用LLM生成MIDI序列 |
| 工作流模式 | 和声智能体按预设流程进行自动配器 | 旋律分析→和弦进行推荐→乐器分配→声部编排 |
| A2A模式 | 四大智能体间实时协作与任务分发 | 旋律智能体生成动机后,和声智能体同步配器,混音智能体预置效果器参数 |
| OpenClaw模式 | 创作者通过自然语言直接指挥智能体 | “把这段旋律改成爵士风格,配萨克斯和钢琴” |
2.2 沉浸光感在音乐制作中的创新应用
传统DAW采用固定深色UI,创作者难以感知音乐的情绪色彩。HarmonyOS 6的沉浸光感特性带来三种创新:
BPM脉搏光效:根据工程BPM动态调整背景光色与脉冲频率
- 慢板期(<<60 BPM):深海蓝,缓慢呼吸(4秒/周期)
- 行板期(60-90 BPM):薄荷绿,平稳律动(2秒/周期)
- 快板期(90-120 BPM):活力橙,快速脉冲(1秒/周期)
- 急板期(>120 BPM):炽烈红,高频闪烁(0.5秒/周期)
频谱能量光效:基于实时FFT频谱分析驱动光效
- 低频能量(20-250Hz):深红色光晕从底部升起,模拟低频震动
- 中频能量(250-4000Hz):暖黄色光晕从中心扩散,模拟人声与乐器
- 高频能量(4000-20000Hz):冰蓝色光晕从顶部洒落,模拟镲片与高频
情绪标签光效:根据作品情绪标签渲染光效主题
- 快乐(Happy):明黄色全屏光效,配合金色粒子
- 悲伤(Sad):靛蓝色全屏光效,配合雨滴效果
- 激昂(Energetic):炽红色全屏光效,配合火焰粒子
- 宁静(Calm):薄荷绿全屏光效,配合水波纹
2.3 悬浮导航的创作适配
传统DAW采用顶部固定工具栏,占用宝贵竖屏空间。HarmonyOS 6的悬浮导航特性带来:
- 底部悬浮页签:不遮挡音轨编辑器,支持透明度三档调节(55%/70%/85%)
- 智能体状态徽章:每个页签实时显示对应智能体运行状态(空闲/思考/执行/完成/异常)
- BPM实时徽章:导航栏中央显示当前BPM与工程时长,点击展开详细数据
- 快捷操作入口:长按导航栏唤起快捷菜单,一键切换创作模式/紧急保存/智能体开关
三、项目实战:"音浪智脑"架构设计
3.1 应用场景与功能规划
面向HarmonyOS PC的专业音乐制作场景,核心功能包括:
| 功能模块 | 技术实现 | 沉浸光感/HMAF应用 |
|---|---|---|
| 主音轨编辑器 | Canvas + AudioEngine |
BPM脉搏光效背景 |
| 悬浮创作导航 | HdsTabs + systemMaterialEffect |
玻璃拟态页签,智能体状态徽章 |
| 旋律智能体 | HMAF Agent Framework Kit | 旋律生成状态光效反馈 |
| 和声智能体 | HMAF + 音乐理论引擎 | 和声进行光效驱动 |
| 混音智能体 | HMAF + 音频处理引擎 | 频谱能量光效反馈 |
| 风格智能体 | HMAF + 风格迁移模型 | 风格主题色光效 |
| 浮动MIDI键盘 | 子窗口 + Canvas |
音符色光效同步 |
| 浮动频谱面板 | 子窗口 + Canvas |
频段色光效同步 |
| 浮动风格选择器 | 子窗口 + Grid |
风格主题色光效 |
3.2 技术架构图
┌─────────────────────────────────────────────────────────────┐
│ HarmonyOS 6 PC 主窗口 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 沉浸光感层 (AmbientLightLayer) │ │
│ │ BPM脉搏光效 + 频谱能量光效 + 情绪标签光效 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 主音轨编辑器 (Canvas + AudioEngine) │ │
│ │ 多轨MIDI + 音频波形 + 自动化包络 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 悬浮创作导航 (FloatNavigation) │ │
│ │ 音轨/和声/混音/风格 页签 + 智能体状态徽章 + BPM徽章 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 浮动MIDI键盘 │ │ 浮动频谱面板 │ │ 浮动风格选择器 │
│ (音符+音高色) │ │ (频段+能量色) │ │ (风格+主题色) │
└───────────────┘ └───────────────┘ └───────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 旋律智能体 │ │ 和声智能体 │ │ 混音智能体 │
│ (MIDI动机生成) │ │ (自动配器编排) │ │ (母带效果处理) │
└───────────────┘ └───────────────┘ └───────────────┘
│
▼
┌───────────────┐
│ 风格智能体 │
│ (风格迁移转换) │
└───────────────┘
四、环境配置与模块依赖
4.1 模块依赖配置
在entry/oh-package.json5中添加以下依赖:
{
"dependencies": {
"@kit.ArkUI": "1.0.0",
"@kit.WindowManagerKit": "1.0.0",
"@kit.SensorServiceKit": "1.0.0",
"@kit.UIDesignKit": "1.0.0",
"@kit.AgentFrameworkKit": "1.0.0",
"@kit.IntentsKit": "1.0.0",
"@kit.AudioKit": "1.0.0",
"@kit.MediaKit": "1.0.0",
"@kit.NNKit": "1.0.0"
}
}
4.2 权限声明(module.json5)
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:mic_permission_reason"
},
{
"name": "ohos.permission.INTERNET",
"reason": "$string:internet_permission_reason"
},
{
"name": "ohos.permission.SYSTEM_FLOAT_WINDOW",
"reason": "$string:float_window_permission_reason"
},
{
"name": "ohos.permission.READ_USER_STORAGE",
"reason": "$string:storage_permission_reason"
},
{
"name": "ohos.permission.WRITE_USER_STORAGE",
"reason": "$string:storage_permission_reason"
}
]
}
}
五、核心组件实战
5.1 窗口沉浸配置(EntryAbility.ets)
// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.WindowManagerKit';
export default class EntryAbility extends UIAbility {
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
const mainWindow = await windowStage.createSubWindow('music_main');
// 启用全屏沉浸模式,隐藏系统标题栏
await mainWindow.setWindowLayoutFullScreen(true);
// 设置窗口背景为透明,允许沉浸光效层透传
await mainWindow.setWindowBackgroundColor('#00000000');
// 加载主页面
windowStage.loadContent('pages/MusicStudioPage', (err) => {
if (err) {
console.error('Failed to load MusicStudioPage:', err.message);
}
});
// 初始化窗口焦点监听,用于光效联动
mainWindow.on('windowFocusChange', (isFocused: boolean) => {
AppStorage.setOrCreate('window_focused', isFocused);
});
}
onWindowStageDestroy(): void {
// 清理音频资源
AudioEngine.getInstance().release();
}
}
5.2 BPM脉搏光效系统(BPMLightEffect.ets)
// entry/src/main/ets/components/BPMLightEffect.ets
import { HdsNavigation, SystemMaterialEffect } from '@kit.UIDesignKit';
// BPM阶段枚举
export enum BPMPhase {
ADAGIO = 'adagio', // 慢板 <60 BPM
ANDANTE = 'andante', // 行板 60-90 BPM
ALLEGRO = 'allegro', // 快板 90-120 BPM
PRESTO = 'presto' // 急板 >120 BPM
}
// 情绪标签枚举
export enum MoodTag {
HAPPY = 'happy', // 快乐
SAD = 'sad', // 悲伤
ENERGETIC = 'energetic', // 激昂
CALM = 'calm' // 宁静
}
// 智能体状态枚举
export enum AgentState {
IDLE = 'idle',
THINKING = 'thinking',
EXECUTING = 'executing',
COMPLETED = 'completed',
ERROR = 'error'
}
@Component
export struct BPMLightEffect {
@Prop currentBPM: number = 120;
@Prop currentMood: MoodTag = MoodTag.CALM;
@Prop spectrumEnergy: number[] = [0, 0, 0]; // 低中高频能量
@State lightIntensity: number = 0.6;
@State pulsePhase: number = 0;
@State beatPhase: number = 0;
// BPM阶段主题色映射
private bpmColors: Map<BPMPhase, string> = new Map([
[BPMPhase.ADAGIO, '#1E3A8A'], // 深海蓝
[BPMPhase.ANDANTE, '#10B981'], // 薄荷绿
[BPMPhase.ALLEGRO, '#F59E0B'], // 活力橙
[BPMPhase.PRESTO, '#EF4444'] // 炽烈红
]);
// 情绪标签色映射
private moodColors: Map<MoodTag, string> = new Map([
[MoodTag.HAPPY, '#FCD34D'], // 明黄色
[MoodTag.SAD, '#6366F1'], // 靛蓝色
[MoodTag.ENERGETIC, '#EF4444'], // 炽红色
[MoodTag.CALM, '#10B981'] // 薄荷绿
]);
// 脉冲周期映射(毫秒,基于BPM计算)
private getPulseDuration(): number {
return (60 / this.currentBPM) * 1000;
}
private getCurrentPhase(): BPMPhase {
if (this.currentBPM < 60) return BPMPhase.ADAGIO;
if (this.currentBPM < 90) return BPMPhase.ANDANTE;
if (this.currentBPM < 120) return BPMPhase.ALLEGRO;
return BPMPhase.PRESTO;
}
private getThemeColor(): string {
// 情绪标签优先于BPM
return this.moodColors.get(this.currentMood) || this.bpmColors.get(this.getCurrentPhase()) || '#1E3A8A';
}
// 频谱能量影响光效强度
private getIntensityBySpectrum(): number {
const avgEnergy = this.spectrumEnergy.reduce((a, b) => a + b, 0) / 3;
return Math.min(1.0, 0.3 + avgEnergy * 0.7);
}
aboutToAppear(): void {
// 启动光效脉冲动画
this.startPulseAnimation();
// 启动节拍动画
this.startBeatAnimation();
}
private startPulseAnimation(): void {
setInterval(() => {
this.pulsePhase = this.pulsePhase === 0 ? 1 : 0;
}, this.getPulseDuration() / 2);
}
private startBeatAnimation(): void {
setInterval(() => {
this.beatPhase = this.beatPhase === 0 ? 1 : 0;
}, this.getPulseDuration());
}
build() {
Stack() {
// 底层:BPM脉搏光效(大面积模糊光晕)
Column()
.width(800)
.height(800)
.backgroundColor(this.getThemeColor())
.blur(200)
.opacity(this.getIntensityBySpectrum() * 0.25)
.position({ x: '50%', y: '30%' })
.anchor('50%')
.scale({
x: this.pulsePhase === 0 ? 1.0 : 1.3,
y: this.pulsePhase === 0 ? 1.0 : 1.3
})
.animation({
duration: this.getPulseDuration(),
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
// 中层:低频能量光效(底部深红)
Column()
.width('100%')
.height(200)
.backgroundColor('#EF4444')
.opacity(this.spectrumEnergy[0] * 0.3)
.blur(80)
.position({ x: 0, y: '80%' })
.linearGradient({
direction: GradientDirection.Top,
colors: [
['#EF4444', 0.0],
['transparent', 1.0]
]
})
.animation({
duration: 100,
curve: Curve.Linear
})
// 中层:中频能量光效(中心暖黄)
Column()
.width(400)
.height(400)
.backgroundColor('#F59E0B')
.opacity(this.spectrumEnergy[1] * 0.2)
.blur(120)
.position({ x: '50%', y: '50%' })
.anchor('50%')
.animation({
duration: 100,
curve: Curve.Linear
})
// 中层:高频能量光效(顶部冰蓝)
Column()
.width('100%')
.height(150)
.backgroundColor('#60A5FA')
.opacity(this.spectrumEnergy[2] * 0.25)
.blur(60)
.position({ x: 0, y: 0 })
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
['#60A5FA', 0.0],
['transparent', 1.0]
]
})
.animation({
duration: 100,
curve: Curve.Linear
})
// 顶层:节拍粒子效果
this.buildBeatParticles()
}
.width('100%')
.height('100%')
.backgroundColor('#050508')
}
@Builder
buildBeatParticles(): void {
ForEach([0, 1, 2, 3], (index: number) => {
Column()
.width(6)
.height(6)
.backgroundColor(this.getThemeColor())
.borderRadius(3)
.opacity(this.beatPhase === 0 ? 0.8 : 0.2)
.position({
x: `${15 + index * 22}%`,
y: `${20 + index * 15}%`
})
.animation({
duration: this.getPulseDuration() / 4,
curve: Curve.EaseOut,
iterations: 1
})
.scale({
x: this.beatPhase === 0 ? 2.0 : 0.5,
y: this.beatPhase === 0 ? 2.0 : 0.5
})
})
}
}
5.3 HMAF四层智能体调度器(MusicAgentScheduler.ets)
// entry/src/main/ets/agents/MusicAgentScheduler.ets
import { hmaf } from '@kit.AgentFrameworkKit';
import { intents } from '@kit.IntentsKit';
// 智能体类型枚举
export enum AgentType {
MELODY = 'melody', // 旋律智能体
HARMONY = 'harmony', // 和声智能体
MIXING = 'mixing', // 混音智能体
STYLE = 'style' // 风格智能体
}
// 智能体人格色彩映射
export enum AgentPersonality {
MELODY = '#FCD34D', // 旋律金
HARMONY = '#8B5CF6', // 和声紫
MIXING = '#06B6D4', // 混音青
STYLE = '#F97316' // 风格橙
}
export class MusicAgentScheduler {
private static instance: MusicAgentScheduler;
private hmafSession: hmaf.AgentSession | null = null;
private intentEngine: intents.IntentEngine | null = null;
// 智能体状态管理
private agentStates: Map<string, AgentState> = new Map([
['melody-1', AgentState.IDLE],
['harmony-1', AgentState.IDLE],
['mixing-1', AgentState.IDLE],
['style-1', AgentState.IDLE]
]);
private constructor() {}
static getInstance(): MusicAgentScheduler {
if (!MusicAgentScheduler.instance) {
MusicAgentScheduler.instance = new MusicAgentScheduler();
}
return MusicAgentScheduler.instance;
}
async initialize(): Promise<void> {
// 初始化HMAF多智能体会话
this.hmafSession = await hmaf.createAgentSession({
mode: hmaf.AgentMode.MULTI_AGENT,
enableDistributed: true,
maxConcurrentAgents: 4
});
// 初始化意图引擎
this.intentEngine = await intents.createIntentEngine({
supportedDomains: ['music_composition', 'harmony_arrangement', 'audio_mixing', 'style_transfer']
});
// 注册四大智能体
await this.registerAgents();
// 启动状态监听
this.startStateMonitoring();
}
private async registerAgents(): Promise<void> {
// 1. 旋律智能体:基于情绪标签生成MIDI动机
await this.hmafSession?.registerAgent({
agentId: 'melody-1',
agentType: AgentType.MELODY,
capabilities: ['melody_generation', 'motif_extraction', 'phrase_extension', 'variation_creation'],
modelConfig: {
modelType: 'llm',
temperature: 0.8,
maxTokens: 1024
}
});
// 2. 和声智能体:自动配器与和弦进行
await this.hmafSession?.registerAgent({
agentId: 'harmony-1',
agentType: AgentType.HARMONY,
capabilities: ['chord_progression', 'instrumentation', 'voicing_arrangement', 'counterpoint'],
modelConfig: {
modelType: 'llm',
temperature: 0.5,
maxTokens: 1024
}
});
// 3. 混音智能体:母带处理与效果器
await this.hmafSession?.registerAgent({
agentId: 'mixing-1',
agentType: AgentType.MIXING,
capabilities: ['eq_adjustment', 'compression', 'reverb_design', 'mastering', 'loudness_optimization'],
modelConfig: {
modelType: 'llm',
temperature: 0.3,
maxTokens: 512
}
});
// 4. 风格智能体:风格迁移与转换
await this.hmafSession?.registerAgent({
agentId: 'style-1',
agentType: AgentType.STYLE,
capabilities: ['style_analysis', 'genre_transfer', 'instrument_swap', 'rhythm_pattern_adaptation'],
modelConfig: {
modelType: 'llm',
temperature: 0.6,
maxTokens: 512
}
});
}
// 生成旋律动机
async generateMelody(mood: string, key: string, bpm: number): Promise<{ midi: number[]; rhythm: number[]; mood: string }> {
this.updateAgentState('melody-1', AgentState.THINKING);
const result = await this.hmafSession?.sendTask({
targetAgent: 'melody-1',
taskType: 'generate_melody',
payload: {
mood: mood, // 'happy', 'sad', 'energetic', 'calm'
key: key, // 'C_major', 'A_minor', etc.
bpm: bpm,
length: 8, // 8小节
complexity: 'medium'
}
});
this.updateAgentState('melody-1', AgentState.COMPLETED);
return {
midi: result?.midiNotes || [],
rhythm: result?.rhythmPattern || [],
mood: result?.detectedMood || mood
};
}
// 自动配器
async arrangeHarmony(melodyMidi: number[], style: string): Promise<{ chords: string[]; instruments: string[]; tracks: Track[] }> {
this.updateAgentState('harmony-1', AgentState.EXECUTING);
const result = await this.hmafSession?.sendTask({
targetAgent: 'harmony-1',
taskType: 'arrange_harmony',
payload: {
melody: melodyMidi,
style: style, // 'pop', 'jazz', 'classical', 'electronic'
maxVoices: 4,
includeBass: true,
includeDrums: true
}
});
this.updateAgentState('harmony-1', AgentState.COMPLETED);
return {
chords: result?.chordProgression || [],
instruments: result?.instruments || [],
tracks: result?.tracks || []
};
}
// 混音母带
async mixMaster(tracks: Track[], targetLoudness: number): Promise<{ eq: EQSettings; compression: CompressionSettings; reverb: ReverbSettings; loudness: number }> {
this.updateAgentState('mixing-1', AgentState.EXECUTING);
const result = await this.hmafSession?.sendTask({
targetAgent: 'mixing-1',
taskType: 'mix_master',
payload: {
tracks: tracks,
targetLoudness: targetLoudness, // LUFS
genre: 'pop',
referenceTrack: null
}
});
this.updateAgentState('mixing-1', AgentState.COMPLETED);
return {
eq: result?.eqSettings || {},
compression: result?.compressionSettings || {},
reverb: result?.reverbSettings || {},
loudness: result?.finalLoudness || 0
};
}
// 风格迁移
async transferStyle(originalMidi: number[], targetStyle: string): Promise<{ midi: number[]; styleFeatures: string[]; confidence: number }> {
this.updateAgentState('style-1', AgentState.THINKING);
const result = await this.hmafSession?.sendTask({
targetAgent: 'style-1',
taskType: 'transfer_style',
payload: {
originalMidi: originalMidi,
targetStyle: targetStyle, // 'jazz', 'rock', 'classical', 'electronic'
preserveMelody: true,
intensity: 0.7
}
});
this.updateAgentState('style-1', AgentState.COMPLETED);
return {
midi: result?.transformedMidi || [],
styleFeatures: result?.styleFeatures || [],
confidence: result?.confidence || 0
};
}
private updateAgentState(agentId: string, state: AgentState): void {
this.agentStates.set(agentId, state);
AppStorage.setOrCreate('agent_state_update', { agentId, state });
}
getAgentState(agentId: string): AgentState {
return this.agentStates.get(agentId) || AgentState.IDLE;
}
private startStateMonitoring(): void {
// 监听智能体状态变化,同步到UI
this.hmafSession?.on('agentStateChange', (event: { agentId: string; state: string }) => {
this.updateAgentState(event.agentId, event.state as AgentState);
});
}
}
// 音轨类型
export interface Track {
id: string;
name: string;
type: 'melody' | 'harmony' | 'bass' | 'drums' | 'fx';
midiData: number[];
instrument: string;
volume: number;
pan: number;
muted: boolean;
solo: boolean;
}
// EQ设置
export interface EQSettings {
lowGain: number;
midGain: number;
highGain: number;
lowFreq: number;
midFreq: number;
highFreq: number;
}
// 压缩设置
export interface CompressionSettings {
threshold: number;
ratio: number;
attack: number;
release: number;
makeupGain: number;
}
// 混响设置
export interface ReverbSettings {
roomSize: number;
damping: number;
wetLevel: number;
dryLevel: number;
}
5.4 悬浮创作导航(MusicFloatNavigation.ets)
// entry/src/main/ets/components/MusicFloatNavigation.ets
import { window } from '@kit.ArkUI';
import { AgentState, AgentType, AgentPersonality } from '../agents/MusicAgentScheduler';
import { BPMPhase } from './BPMLightEffect';
interface NavItem {
id: string;
icon: Resource;
label: string;
page: string;
agentType?: AgentType;
}
@Component
export struct MusicFloatNavigation {
@State currentIndex: number = 0;
@State navTransparency: number = 0.70;
@State isExpanded: boolean = false;
@State bottomAvoidHeight: number = 0;
@State currentBPM: number = 120;
@State projectDuration: number = 0; // 工程时长(秒)
@State currentPhase: BPMPhase = BPMPhase.ALLEGRO;
@State agentStates: Map<string, AgentState> = new Map([
['melody-1', AgentState.IDLE],
['harmony-1', AgentState.IDLE],
['mixing-1', AgentState.IDLE],
['style-1', AgentState.IDLE]
]);
private navItems: NavItem[] = [
{ id: 'tracks', icon: $r('app.media.ic_tracks'), label: '音轨', page: 'TracksPage' },
{ id: 'harmony', icon: $r('app.media.ic_piano'), label: '和声', page: 'HarmonyPage', agentType: AgentType.HARMONY },
{ id: 'mixing', icon: $r('app.media.ic_sliders'), label: '混音', page: 'MixingPage', agentType: AgentType.MIXING },
{ id: 'style', icon: $r('app.media.ic_style'), label: '风格', page: 'StylePage', agentType: AgentType.STYLE },
{ id: 'settings', icon: $r('app.media.ic_settings'), label: '设置', page: 'SettingsPage' }
];
aboutToAppear(): void {
this.getBottomAvoidArea();
// 监听智能体状态更新
AppStorage.watch('agent_state_update', (update: { agentId: string; state: string }) => {
this.agentStates.set(update.agentId, update.state as AgentState);
});
// 监听工程数据
AppStorage.watch('project_metrics', (metrics: { bpm: number; duration: number }) => {
this.currentBPM = metrics.bpm;
this.projectDuration = metrics.duration;
this.updateBPMPhase();
});
}
private updateBPMPhase(): void {
if (this.currentBPM < 60) this.currentPhase = BPMPhase.ADAGIO;
else if (this.currentBPM < 90) this.currentPhase = BPMPhase.ANDANTE;
else if (this.currentBPM < 120) this.currentPhase = BPMPhase.ALLEGRO;
else this.currentPhase = BPMPhase.PRESTO;
}
private async getBottomAvoidArea(): Promise<void> {
try {
const mainWindow = await window.getLastWindow();
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
this.bottomAvoidHeight = avoidArea.bottomRect.height;
} catch (error) {
console.error('Failed to get avoid area:', error);
}
}
private getAgentStateForNavItem(item: NavItem): AgentState {
if (!item.agentType) return AgentState.IDLE;
const agentIdMap: Record<string, string> = {
[AgentType.MELODY]: 'melody-1',
[AgentType.HARMONY]: 'harmony-1',
[AgentType.MIXING]: 'mixing-1',
[AgentType.STYLE]: 'style-1'
};
return this.agentStates.get(agentIdMap[item.agentType]) || AgentState.IDLE;
}
private getPhaseColor(): string {
const colors: Record<BPMPhase, string> = {
[BPMPhase.ADAGIO]: '#1E3A8A',
[BPMPhase.ANDANTE]: '#10B981',
[BPMPhase.ALLEGRO]: '#F59E0B',
[BPMPhase.PRESTO]: '#EF4444'
};
return colors[this.currentPhase];
}
private getStateBadgeColor(state: AgentState): string {
const colors: Record<<AgentState, string> = {
[AgentState.IDLE]: '#888888',
[AgentState.THINKING]: '#8B5CF6',
[AgentState.EXECUTING]: '#F97316',
[AgentState.COMPLETED]: '#10B981',
[AgentState.ERROR]: '#EF4444'
};
return colors[state] || '#888888';
}
private getStateBadgeAnimation(state: AgentState): object {
switch (state) {
case AgentState.THINKING:
return {
duration: 1500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
case AgentState.EXECUTING:
return {
duration: 800,
curve: Curve.Linear,
iterations: -1
};
case AgentState.ERROR:
return {
duration: 400,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
default:
return { duration: 0 };
}
}
private formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
Column() {
this.contentBuilder()
}
.padding({ bottom: this.bottomAvoidHeight + 90 })
// 悬浮导航容器
Column() {
Stack() {
// 玻璃拟态背景层
Column()
.width('100%')
.height('100%')
.backgroundBlurStyle(BlurStyle.REGULAR)
.opacity(this.navTransparency)
.backdropFilter($r('sys.blur.20'))
// 渐变叠加层
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Top,
colors: [
['rgba(255,255,255,0.15)', 0.0],
['rgba(255,255,255,0.05)', 1.0]
]
})
}
.width('100%')
.height('100%')
.borderRadius(28)
.shadow({
radius: 24,
color: 'rgba(0,0,0,0.25)',
offsetX: 0,
offsetY: -6
})
Column() {
// 工程信息栏(展开时显示)
if (this.isExpanded) {
Row() {
Column() {
Text(`${this.currentBPM} BPM`)
.fontSize(13)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text('当前速度')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
.layoutWeight(1)
Column() {
Text(this.formatDuration(this.projectDuration))
.fontSize(13)
.fontColor('#FCD34D')
.fontWeight(FontWeight.Bold)
Text('工程时长')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
.layoutWeight(1)
Column() {
Text(this.getPhaseLabel())
.fontSize(13)
.fontColor(this.getPhaseColor())
.fontWeight(FontWeight.Bold)
Text('速度阶段')
.fontSize(10)
.fontColor('rgba(255,255,255,0.6)')
}
.layoutWeight(1)
}
.width('100%')
.height(50)
.padding({ left: 16, right: 16 })
.backgroundColor('rgba(0,0,0,0.2)')
.borderRadius({ topLeft: 28, topRight: 28 })
}
// 导航页签
Row() {
ForEach(this.navItems, (item: NavItem, index: number) => {
Column() {
Stack() {
Image(item.icon)
.width(26)
.height(26)
.fillColor(this.currentIndex === index ? this.getPhaseColor() : '#AAAAAA')
// 智能体状态徽章
if (item.agentType && this.getAgentStateForNavItem(item) !== AgentState.IDLE) {
Column()
.width(10)
.height(10)
.backgroundColor(this.getStateBadgeColor(this.getAgentStateForNavItem(item)))
.borderRadius(5)
.position({ x: 20, y: -4 })
.shadow({
radius: 8,
color: this.getStateBadgeColor(this.getAgentStateForNavItem(item)),
offsetX: 0,
offsetY: 0
})
.animation(this.getStateBadgeAnimation(this.getAgentStateForNavItem(item)))
}
}
.width(44)
.height(44)
Text(item.label)
.fontSize(11)
.fontColor(this.currentIndex === index ? this.getPhaseColor() : '#BBBBBB')
.margin({ top: 4 })
}
.layoutWeight(1)
.onClick(() => {
this.currentIndex = index;
this.triggerHapticFeedback();
AppStorage.setOrCreate('nav_page_change', item.page);
})
})
}
.width('100%')
.height(this.isExpanded ? 70 : 80)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceAround)
// 透明度调节(展开时显示)
if (this.isExpanded) {
Row() {
Text('透明度')
.fontSize(12)
.fontColor('rgba(255,255,255,0.7)')
.margin({ right: 8 })
Slider({
value: this.navTransparency * 100,
min: 55,
max: 85,
step: 15,
style: SliderStyle.InSet
})
.width(120)
.onChange((value: number) => {
this.navTransparency = value / 100;
})
Text(`${Math.round(this.navTransparency * 100)}%`)
.fontSize(12)
.fontColor('rgba(255,255,255,0.7)')
.margin({ left: 8 })
}
.width('100%')
.height(40)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
}
.width('92%')
.height(this.isExpanded ? 160 : 80)
.margin({ bottom: this.bottomAvoidHeight + 12, left: '4%', right: '4%' })
.animation({
duration: 350,
curve: Curve.Spring,
iterations: 1
})
.gesture(
LongPressGesture({ duration: 600 })
.onAction(() => {
this.isExpanded = !this.isExpanded;
})
)
}
.width('100%')
.height('100%')
}
private getPhaseLabel(): string {
const labels: Record<BPMPhase, string> = {
[BPMPhase.ADAGIO]: '慢板',
[BPMPhase.ANDANTE]: '行板',
[BPMPhase.ALLEGRO]: '快板',
[BPMPhase.PRESTO]: '急板'
};
return labels[this.currentPhase];
}
@BuilderParam contentBuilder: () => void = this.defaultContentBuilder;
@Builder
defaultContentBuilder(): void {
Column() {
Text('音轨编辑区域')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
private triggerHapticFeedback(): void {
try {
import('@kit.SensorServiceKit').then(sensor => {
sensor.vibrator.startVibration({
type: 'time',
duration: 40
}, { id: 0 });
});
} catch (error) {
console.error('Haptic feedback failed:', error);
}
}
}
5.5 音轨编辑器(TrackEditor.ets)
// entry/src/main/ets/components/TrackEditor.ets
import { MusicAgentScheduler, Track } from '../agents/MusicAgentScheduler';
@Component
export struct TrackEditor {
@State tracks: Track[] = [
{ id: '1', name: '旋律', type: 'melody', midiData: [], instrument: 'piano', volume: 80, pan: 0, muted: false, solo: false },
{ id: '2', name: '和声', type: 'harmony', midiData: [], instrument: 'strings', volume: 70, pan: -20, muted: false, solo: false },
{ id: '3', name: '贝斯', type: 'bass', midiData: [], instrument: 'bass', volume: 75, pan: 0, muted: false, solo: false },
{ id: '4', name: '鼓组', type: 'drums', midiData: [], instrument: 'drums', volume: 85, pan: 0, muted: false, solo: false }
];
@State selectedTrack: string = '';
@State isPlaying: boolean = false;
@State currentTime: number = 0;
private scheduler: MusicAgentScheduler = MusicAgentScheduler.getInstance();
aboutToAppear(): void {
// 监听播放状态
AppStorage.watch('play_state', (state: boolean) => {
this.isPlaying = state;
});
}
private getTrackColor(type: string): string {
const colors: Record<string, string> = {
'melody': '#FCD34D',
'harmony': '#8B5CF6',
'bass': '#06B6D4',
'drums': '#EF4444',
'fx': '#10B981'
};
return colors[type] || '#888888';
}
build() {
Column() {
// 时间轴标尺
Row() {
Text('0:00')
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
.width(80)
ForEach([1, 2, 3, 4, 5, 6, 7, 8], (bar: number) => {
Text(`${bar}`)
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
.layoutWeight(1)
.textAlign(TextAlign.Center)
})
}
.width('100%')
.height(30)
.padding({ left: 80, right: 16 })
.backgroundColor('rgba(255,255,255,0.03)')
// 播放头
if (this.isPlaying) {
Column()
.width(2)
.height('100%')
.backgroundColor('#EF4444')
.position({ x: 80 + (this.currentTime % 8) * 100, y: 0 })
.zIndex(100)
.animation({
duration: 100,
curve: Curve.Linear
})
}
// 音轨列表
List() {
ForEach(this.tracks, (track: Track) => {
ListItem() {
Row() {
// 轨道控制区
Column() {
Row({ space: 8 }) {
// 静音按钮
Text('M')
.fontSize(10)
.fontColor(track.muted ? '#EF4444' : 'rgba(255,255,255,0.5)')
.backgroundColor(track.muted ? 'rgba(239,68,68,0.2)' : 'rgba(255,255,255,0.05)')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(4)
.onClick(() => {
const index = this.tracks.findIndex(t => t.id === track.id);
this.tracks[index] = { ...track, muted: !track.muted };
})
// 独奏按钮
Text('S')
.fontSize(10)
.fontColor(track.solo ? '#FCD34D' : 'rgba(255,255,255,0.5)')
.backgroundColor(track.solo ? 'rgba(252,211,77,0.2)' : 'rgba(255,255,255,0.05)')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.borderRadius(4)
.onClick(() => {
const index = this.tracks.findIndex(t => t.id === track.id);
this.tracks[index] = { ...track, solo: !track.solo };
})
}
Text(track.name)
.fontSize(12)
.fontColor(this.getTrackColor(track.type))
.fontWeight(FontWeight.Medium)
.margin({ top: 8 })
Text(track.instrument)
.fontSize(10)
.fontColor('rgba(255,255,255,0.4)')
}
.width(80)
.height(80)
.padding(8)
.backgroundColor('rgba(255,255,255,0.03)')
.borderRadius({ topLeft: 8, bottomLeft: 8 })
// 音轨内容区
Stack() {
// 背景网格
Row() {
ForEach([0, 1, 2, 3], (i: number) => {
Column()
.width('25%')
.height('100%')
.border({ width: { right: 1 }, color: 'rgba(255,255,255,0.05)' })
})
}
.width('100%')
.height('100%')
// MIDI音符可视化
if (track.midiData.length > 0) {
this.buildMidiVisualization(track)
} else {
Text('点击生成旋律...')
.fontSize(11)
.fontColor('rgba(255,255,255,0.3)')
}
// 选中高亮
if (this.selectedTrack === track.id) {
Column()
.width('100%')
.height('100%')
.border({ width: 2, color: this.getTrackColor(track.type) })
.backgroundColor(`${this.getTrackColor(track.type)}10`)
}
}
.width('100%')
.height(80)
.backgroundColor('rgba(255,255,255,0.02)')
.borderRadius({ topRight: 8, bottomRight: 8 })
.onClick(() => {
this.selectedTrack = track.id;
})
}
.width('100%')
.margin({ bottom: 4 })
}
})
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16 })
// 底部控制栏
Row({ space: 16 }) {
Button(this.isPlaying ? '⏸️ 暂停' : '▶️ 播放')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor(this.isPlaying ? '#F59E0B' : '#10B981')
.padding({ left: 24, right: 24, top: 10, bottom: 10 })
.borderRadius(20)
.onClick(() => {
this.isPlaying = !this.isPlaying;
AppStorage.setOrCreate('play_state', this.isPlaying);
})
Button('🎵 生成旋律')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#8B5CF6')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.onClick(() => {
this.generateMelodyForSelectedTrack();
})
Button('🎹 自动配器')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#06B6D4')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.onClick(() => {
this.arrangeHarmonyForProject();
})
Button('🎚️ 母带处理')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('#F97316')
.padding({ left: 20, right: 20, top: 10, bottom: 10 })
.borderRadius(20)
.onClick(() => {
this.mixMasterProject();
})
}
.width('100%')
.height(60)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(10, 10, 15, 0.95)')
.borderRadius(16)
}
@Builder
buildMidiVisualization(track: Track): void {
Row() {
ForEach(track.midiData.slice(0, 16), (note: number, index: number) => {
Column()
.width(20)
.height(Math.max(10, note / 2))
.backgroundColor(this.getTrackColor(track.type))
.borderRadius(4)
.margin({ right: 4 })
.opacity(track.muted ? 0.3 : 1.0)
})
}
.width('100%')
.height('100%')
.padding(8)
.alignItems(VerticalAlign.Bottom)
}
private async generateMelodyForSelectedTrack(): Promise<void> {
if (!this.selectedTrack) return;
const result = await this.scheduler.generateMelody('happy', 'C_major', this.currentBPM);
const index = this.tracks.findIndex(t => t.id === this.selectedTrack);
if (index !== -1) {
this.tracks[index] = {
...this.tracks[index],
midiData: result.midi
};
}
}
private async arrangeHarmonyForProject(): Promise<void> {
const melodyTrack = this.tracks.find(t => t.type === 'melody');
if (!melodyTrack || melodyTrack.midiData.length === 0) return;
const result = await this.scheduler.arrangeHarmony(melodyTrack.midiData, 'pop');
// 更新和声、贝斯、鼓组轨道
result.tracks.forEach((newTrack: Track) => {
const index = this.tracks.findIndex(t => t.type === newTrack.type);
if (index !== -1) {
this.tracks[index] = {
...this.tracks[index],
midiData: newTrack.midiData,
instrument: newTrack.instrument
};
}
});
}
private async mixMasterProject(): Promise<void> {
const result = await this.scheduler.mixMaster(this.tracks, -14);
// 应用混音参数到各轨道
this.tracks = this.tracks.map(track => ({
...track,
volume: Math.min(100, track.volume + 5)
}));
}
}
5.6 浮动频谱面板(SpectrumPanel.ets)
// entry/src/main/ets/components/SpectrumPanel.ets
import { AudioEngine } from '../utils/AudioEngine';
@Component
export struct SpectrumPanel {
@State spectrumData: number[] = new Array(64).fill(0);
@State peakFrequency: number = 0;
@State peakEnergy: number = 0;
private audioEngine: AudioEngine = AudioEngine.getInstance();
private animationTimer: number = 0;
aboutToAppear(): void {
this.startSpectrumAnalysis();
}
aboutToDisappear(): void {
clearInterval(this.animationTimer);
}
private startSpectrumAnalysis(): void {
this.animationTimer = setInterval(() => {
// 模拟FFT频谱数据
this.spectrumData = this.spectrumData.map(() => Math.random() * 0.8);
// 计算峰值
this.peakEnergy = Math.max(...this.spectrumData);
this.peakFrequency = this.spectrumData.indexOf(this.peakEnergy) * 343; // 约20Hz-20kHz范围
// 同步频谱能量到全局光效
const lowEnergy = this.spectrumData.slice(0, 16).reduce((a, b) => a + b, 0) / 16;
const midEnergy = this.spectrumData.slice(16, 40).reduce((a, b) => a + b, 0) / 24;
const highEnergy = this.spectrumData.slice(40, 64).reduce((a, b) => a + b, 0) / 24;
AppStorage.setOrCreate('spectrum_energy', [lowEnergy, midEnergy, highEnergy]);
}, 50); // 20fps更新
}
private getBarColor(index: number, value: number): string {
if (index < 16) return `rgba(239, 68, 68, ${value})`; // 低频-红
if (index < 40) return `rgba(245, 158, 11, ${value})`; // 中频-橙
return `rgba(96, 165, 250, ${value})`; // 高频-蓝
}
build() {
Column() {
// 面板标题栏
Row() {
Text('📊 实时频谱')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Column() {
Text(`${this.peakFrequency}Hz`)
.fontSize(11)
.fontColor('#FCD34D')
Text(`峰值: ${(this.peakEnergy * 100).toFixed(1)}%`)
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
}
}
.width('100%')
.height(50)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceBetween)
// 频谱柱状图
Row({ space: 2 }) {
ForEach(this.spectrumData, (value: number, index: number) => {
Column()
.width(4)
.height(`${value * 100}%`)
.backgroundColor(this.getBarColor(index, value))
.borderRadius({ topLeft: 2, topRight: 2 })
.animation({
duration: 50,
curve: Curve.Linear
})
})
}
.width('100%')
.height(200)
.padding({ left: 12, right: 12 })
.alignItems(VerticalAlign.Bottom)
// 频段标签
Row() {
Text('低频 20-250Hz')
.fontSize(10)
.fontColor('#EF4444')
.layoutWeight(1)
.textAlign(TextAlign.Center)
Text('中频 250-4kHz')
.fontSize(10)
.fontColor('#F59E0B')
.layoutWeight(1)
.textAlign(TextAlign.Center)
Text('高频 4k-20kHz')
.fontSize(10)
.fontColor('#60A5FA')
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.height(30)
.padding({ left: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor('rgba(10, 10, 15, 0.95)')
.borderRadius(16)
}
}
5.7 多窗口光效同步管理器(WindowLightSync.ets)
// entry/src/main/ets/utils/WindowLightSync.ets
import { window } from '@kit.WindowManagerKit';
export class WindowLightSync {
private static instance: WindowLightSync;
private subWindows: Map<string, window.Window> = new Map();
private currentTheme: string = '#1E3A8A';
private currentIntensity: number = 0.6;
private constructor() {}
static getInstance(): WindowLightSync {
if (!WindowLightSync.instance) {
WindowLightSync.instance = new WindowLightSync();
}
return WindowLightSync.instance;
}
async registerSubWindow(name: string, win: window.Window): Promise<void> {
this.subWindows.set(name, win);
await this.syncLightToWindow(win);
}
async syncGlobalLightEffect(themeColor: string, intensity?: number): Promise<void> {
this.currentTheme = themeColor;
if (intensity !== undefined) {
this.currentIntensity = intensity;
}
for (const [name, win] of this.subWindows) {
await this.syncLightToWindow(win);
}
AppStorage.setOrCreate('global_theme_color', this.currentTheme);
AppStorage.setOrCreate('global_light_intensity', this.currentIntensity);
}
private async syncLightToWindow(win: window.Window): Promise<void> {
try {
await win.setWindowBackgroundColor(`${this.currentTheme}20`);
await win.setWindowShadow({
radius: 20,
color: `${this.currentTheme}40`,
offsetX: 0,
offsetY: 4
});
} catch (error) {
console.error(`Failed to sync light to window:`, error);
}
}
async handleFocusChange(focusedWindowName: string): Promise<void> {
for (const [name, win] of this.subWindows) {
const isFocused = name === focusedWindowName;
const intensity = isFocused ? this.currentIntensity : this.currentIntensity * 0.4;
try {
await win.setWindowBackgroundColor(`${this.currentTheme}${Math.floor(intensity * 255).toString(16).padStart(2, '0')}`);
} catch (error) {
console.error(`Failed to handle focus change:`, error);
}
}
}
async createMidiKeyboardWindow(): Promise<<window.Window> {
const keyboardWindow = await window.createWindow({
name: 'midi_keyboard_float',
windowType: window.WindowType.TYPE_FLOAT,
ctx: getContext()
});
await keyboardWindow.moveWindowTo(1200, 100);
await keyboardWindow.resize(320, 200);
await keyboardWindow.setWindowLayoutFullScreen(true);
await keyboardWindow.setWindowBackgroundColor('#00000000');
this.registerSubWindow('midi_keyboard', keyboardWindow);
return keyboardWindow;
}
async createSpectrumWindow(): Promise<<window.Window> {
const spectrumWindow = await window.createWindow({
name: 'spectrum_float',
windowType: window.WindowType.TYPE_FLOAT,
ctx: getContext()
});
await spectrumWindow.moveWindowTo(50, 100);
await spectrumWindow.resize(300, 320);
await spectrumWindow.setWindowLayoutFullScreen(true);
await spectrumWindow.setWindowBackgroundColor('#00000000');
this.registerSubWindow('spectrum', spectrumWindow);
return spectrumWindow;
}
async createStyleSelectorWindow(): Promise<<window.Window> {
const styleWindow = await window.createWindow({
name: 'style_selector_float',
windowType: window.WindowType.TYPE_FLOAT,
ctx: getContext()
});
await styleWindow.moveWindowTo(50, 450);
await styleWindow.resize(300, 280);
await styleWindow.setWindowLayoutFullScreen(true);
await styleWindow.setWindowBackgroundColor('#00000000');
this.registerSubWindow('style_selector', styleWindow);
return styleWindow;
}
}
5.8 主页面集成(MusicStudioPage.ets)
// entry/src/main/ets/pages/MusicStudioPage.ets
import { BPMLightEffect, BPMPhase, MoodTag } from '../components/BPMLightEffect';
import { MusicFloatNavigation } from '../components/MusicFloatNavigation';
import { TrackEditor } from '../components/TrackEditor';
import { SpectrumPanel } from '../components/SpectrumPanel';
import { WindowLightSync } from '../utils/WindowLightSync';
import { MusicAgentScheduler } from '../agents/MusicAgentScheduler';
@Entry
@Component
struct MusicStudioPage {
@State currentPage: string = 'TracksPage';
@State currentBPM: number = 120;
@State projectDuration: number = 0;
@State currentMood: MoodTag = MoodTag.CALM;
@State spectrumEnergy: number[] = [0, 0, 0];
@State isPlaying: boolean = false;
private windowSync: WindowLightSync = WindowLightSync.getInstance();
private scheduler: MusicAgentScheduler = MusicAgentScheduler.getInstance();
private playTimer: number = 0;
aboutToAppear(): async () => {
await this.scheduler.initialize();
await this.initializeWindows();
this.startProjectTimer();
this.startSpectrumSync();
AppStorage.watch('nav_page_change', (page: string) => {
this.currentPage = page;
});
}
private async initializeWindows(): Promise<void> {
await this.windowSync.createMidiKeyboardWindow();
await this.windowSync.createSpectrumWindow();
await this.windowSync.createStyleSelectorWindow();
}
private startProjectTimer(): void {
setInterval(() => {
if (this.isPlaying) {
this.projectDuration++;
AppStorage.setOrCreate('project_metrics', {
bpm: this.currentBPM,
duration: this.projectDuration
});
const phase = this.getCurrentPhase();
const themeColor = this.getPhaseColor(phase);
this.windowSync.syncGlobalLightEffect(themeColor, this.getIntensityBySpectrum());
}
}, 1000);
}
private startSpectrumSync(): void {
AppStorage.watch('spectrum_energy', (energy: number[]) => {
this.spectrumEnergy = energy;
});
}
private getCurrentPhase(): BPMPhase {
if (this.currentBPM < 60) return BPMPhase.ADAGIO;
if (this.currentBPM < 90) return BPMPhase.ANDANTE;
if (this.currentBPM < 120) return BPMPhase.ALLEGRO;
return BPMPhase.PRESTO;
}
private getPhaseColor(phase: BPMPhase): string {
const colors: Record<BPMPhase, string> = {
[BPMPhase.ADAGIO]: '#1E3A8A',
[BPMPhase.ANDANTE]: '#10B981',
[BPMPhase.ALLEGRO]: '#F59E0B',
[BPMPhase.PRESTO]: '#EF4444'
};
return colors[phase];
}
private getIntensityBySpectrum(): number {
const avgEnergy = this.spectrumEnergy.reduce((a, b) => a + b, 0) / 3;
return Math.min(1.0, 0.3 + avgEnergy * 0.7);
}
build() {
Stack() {
// 底层:沉浸光效层
BPMLightEffect({
currentBPM: this.currentBPM,
currentMood: this.currentMood,
spectrumEnergy: this.spectrumEnergy
})
// 中层:主内容区域
Column() {
this.buildHeader()
Stack() {
if (this.currentPage === 'TracksPage') {
TrackEditor()
} else if (this.currentPage === 'HarmonyPage') {
this.buildHarmonyPage()
} else if (this.currentPage === 'MixingPage') {
this.buildMixingPage()
} else if (this.currentPage === 'StylePage') {
this.buildStylePage()
}
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
// 顶层:悬浮导航
MusicFloatNavigation({
contentBuilder: () => {}
})
}
.width('100%')
.height('100%')
.backgroundColor('#050508')
.expandSafeArea(
[SafeAreaType.SYSTEM],
[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
)
}
@Builder
buildHeader(): void {
Row() {
Column() {
Text('🎵 音浪智脑')
.fontSize(20)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
Text(`BPM: ${this.currentBPM} | 时长: ${this.formatDuration(this.projectDuration)}`)
.fontSize(12)
.fontColor('rgba(255,255,255,0.6)')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
Row({ space: 12 }) {
// BPM调节
Slider({
value: this.currentBPM,
min: 40,
max: 200,
step: 1,
style: SliderStyle.InSet
})
.width(120)
.onChange((value: number) => {
this.currentBPM = Math.round(value);
})
// 情绪标签选择
Select([
{ value: '快乐', icon: $r('app.media.ic_happy') },
{ value: '悲伤', icon: $r('app.media.ic_sad') },
{ value: '激昂', icon: $r('app.media.ic_energetic') },
{ value: '宁静', icon: $r('app.media.ic_calm') }
])
.selected(this.getMoodIndex())
.fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.1)')
.borderRadius(12)
.onSelect((index: number) => {
const moods = [MoodTag.HAPPY, MoodTag.SAD, MoodTag.ENERGETIC, MoodTag.CALM];
this.currentMood = moods[index];
})
Button('智能体面板')
.fontSize(13)
.fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.1)')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(20)
.onClick(() => {
AppStorage.setOrCreate('show_agent_panel', true);
})
}
}
.width('100%')
.height(70)
.padding({ left: 24, right: 24 })
.backgroundBlurStyle(BlurStyle.REGULAR)
.backdropFilter($r('sys.blur.20'))
.border({
width: { bottom: 1 },
color: 'rgba(255,255,255,0.1)'
})
.justifyContent(FlexAlign.SpaceBetween)
}
private getMoodIndex(): number {
const moods = [MoodTag.HAPPY, MoodTag.SAD, MoodTag.ENERGETIC, MoodTag.CALM];
return moods.indexOf(this.currentMood);
}
private formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
@Builder
buildHarmonyPage(): void {
Column() {
Text('🎹 和声编排')
.fontSize(18)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
// 和弦进行可视化
Row({ space: 12 }) {
ForEach(['C', 'G', 'Am', 'F'], (chord: string, index: number) => {
Column() {
Text(chord)
.fontSize(20)
.fontColor('#8B5CF6')
.fontWeight(FontWeight.Bold)
Text(`${index + 1}小节`)
.fontSize(11)
.fontColor('rgba(255,255,255,0.5)')
}
.width(80)
.height(80)
.backgroundColor('rgba(139, 92, 246, 0.1)')
.borderRadius(12)
.border({ width: 2, color: 'rgba(139, 92, 246, 0.3)' })
.justifyContent(FlexAlign.Center)
})
}
.margin({ bottom: 20 })
// 乐器分配
List() {
ForEach([
{ name: '钢琴', role: '旋律', volume: 80 },
{ name: '弦乐', role: '铺底', volume: 70 },
{ name: '贝斯', role: '低音', volume: 75 },
{ name: '鼓组', role: '节奏', volume: 85 }
], (instrument: { name: string; role: string; volume: number }) => {
ListItem() {
Row() {
Column() {
Text(instrument.name)
.fontSize(14)
.fontColor('#FFFFFF')
Text(instrument.role)
.fontSize(11)
.fontColor('rgba(255,255,255,0.5)')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Slider({
value: instrument.volume,
min: 0,
max: 100,
step: 1
})
.width(120)
Text(`${instrument.volume}%`)
.fontSize(12)
.fontColor('#FFFFFF')
.width(40)
}
.width('100%')
.padding(12)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
}
.margin({ bottom: 8 })
})
}
.width('90%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
@Builder
buildMixingPage(): void {
Column() {
Text('🎚️ 混音母带')
.fontSize(18)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
// EQ可视化
Column() {
Text('均衡器 (EQ)')
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ bottom: 12 })
Row({ space: 16 }) {
this.buildEQBand('低频', '#EF4444', 80)
this.buildEQBand('中频', '#F59E0B', 65)
this.buildEQBand('高频', '#60A5FA', 70)
}
}
.width('90%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.margin({ bottom: 16 })
// 压缩器
Column() {
Text('压缩器')
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ bottom: 12 })
Row({ space: 16 }) {
this.buildCompressorParam('阈值', '-18dB', '#06B6D4')
this.buildCompressorParam('比率', '4:1', '#06B6D4')
this.buildCompressorParam('攻击', '10ms', '#06B6D4')
this.buildCompressorParam('释放', '100ms', '#06B6D4')
}
}
.width('90%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.margin({ bottom: 16 })
// 响度表
Column() {
Text('响度表 (LUFS)')
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ bottom: 12 })
Row() {
Text('-14.2')
.fontSize(36)
.fontColor('#10B981')
.fontWeight(FontWeight.Bold)
Text('LUFS')
.fontSize(14)
.fontColor('rgba(255,255,255,0.5)')
.margin({ left: 8 })
}
// 响度条
Row() {
ForEach([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], (i: number) => {
Column()
.width(20)
.height(20)
.backgroundColor(i < 7 ? '#10B981' : i < 9 ? '#F59E0B' : '#EF4444')
.borderRadius(4)
.margin({ right: 4 })
})
}
}
.width('90%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
Button('🎯 自动母带')
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#F97316')
.padding({ left: 32, right: 32, top: 12, bottom: 12 })
.borderRadius(24)
.margin({ top: 20 })
.onClick(() => {
// 触发混音智能体自动母带
})
}
.width('100%')
.height('100%')
}
@Builder
buildEQBand(label: string, color: string, value: number): void {
Column() {
Text(label)
.fontSize(12)
.fontColor('#FFFFFF')
.margin({ bottom: 8 })
Slider({
value: value,
min: -12,
max: 12,
step: 0.5,
direction: Axis.Vertical
})
.height(120)
Text(`${value > 0 ? '+' : ''}${value}dB`)
.fontSize(11)
.fontColor(color)
.margin({ top: 8 })
}
}
@Builder
buildCompressorParam(label: string, value: string, color: string): void {
Column() {
Text(value)
.fontSize(16)
.fontColor(color)
.fontWeight(FontWeight.Bold)
Text(label)
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
.margin({ top: 4 })
}
.width(70)
.height(60)
.backgroundColor('rgba(255,255,255,0.03)')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
@Builder
buildStylePage(): void {
Column() {
Text('🎨 风格迁移')
.fontSize(18)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
Grid() {
GridItem() {
this.buildStyleCard('流行', 'Pop', '#F472B6', '🎤')
}
GridItem() {
this.buildStyleCard('爵士', 'Jazz', '#F59E0B', '🎷')
}
GridItem() {
this.buildStyleCard('古典', 'Classical', '#8B5CF6', '🎻')
}
GridItem() {
this.buildStyleCard('电子', 'Electronic', '#06B6D4', '🎹')
}
GridItem() {
this.buildStyleCard('摇滚', 'Rock', '#EF4444', '🎸')
}
GridItem() {
this.buildStyleCard('民谣', 'Folk', '#10B981', '🪕')
}
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.width('90%')
.height(300)
.gap(12)
// 风格强度滑块
Column() {
Text('迁移强度')
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ bottom: 12 })
Slider({
value: 70,
min: 0,
max: 100,
step: 1
})
.width('80%')
Text('70% - 保留旋律,改变配器风格')
.fontSize(11)
.fontColor('rgba(255,255,255,0.5)')
.margin({ top: 8 })
}
.width('90%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.margin({ top: 20 })
Button('🔄 应用风格迁移')
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor('#8B5CF6')
.padding({ left: 32, right: 32, top: 12, bottom: 12 })
.borderRadius(24)
.margin({ top: 20 })
.onClick(() => {
// 触发风格智能体迁移
})
}
.width('100%')
.height('100%')
}
@Builder
buildStyleCard(name: string, enName: string, color: string, icon: string): void {
Column() {
Text(icon)
.fontSize(32)
.margin({ bottom: 8 })
Text(name)
.fontSize(14)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
Text(enName)
.fontSize(11)
.fontColor('rgba(255,255,255,0.5)')
.margin({ top: 4 })
}
.width('100%')
.height('100%')
.backgroundColor(`${color}15`)
.borderRadius(12)
.border({ width: 2, color: `${color}30` })
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 选择风格
})
}
}
六、关键技术总结
6.1 HMAF音乐制作智能体开发清单
| 技术点 | API/方法 | 应用场景 |
|---|---|---|
| 智能体会话创建 | hmaf.createAgentSession({ mode: MULTI_AGENT }) |
四层智能体协同创作 |
| 意图解析 | intents.createIntentEngine({ supportedDomains }) |
创作者指令意图识别 |
| 任务分发 | hmafSession.sendTask({ targetAgent, taskType }) |
智能体间创作任务调度 |
| 状态监听 | AppStorage 全局状态回调 |
跨组件创作状态同步 |
| 分布式协同 | enableDistributed: true |
多设备协作创作 |
| LLM旋律生成 | modelType: 'llm' |
旋律智能体生成MIDI动机 |
| 和声编排 | modelType: 'llm' |
和声智能体自动配器 |
| 风格迁移 | modelType: 'llm' |
风格智能体转换音乐风格 |
6.2 沉浸光感实现清单
| 技术点 | API/方法 | 应用场景 |
|---|---|---|
| 系统材质效果 | systemMaterialEffect: SystemMaterialEffect.IMMERSIVE |
标题栏沉浸效果 |
| 背景模糊 | backgroundBlurStyle(BlurStyle.REGULAR) |
悬浮导航玻璃拟态 |
| 背景滤镜 | backdropFilter($r('sys.blur.20')) |
精细模糊控制 |
| 安全区扩展 | expandSafeArea([SafeAreaType.SYSTEM], [...]) |
全屏沉浸布局 |
| 窗口沉浸 | setWindowLayoutFullScreen(true) |
无边框模式 |
| 光效动画 | animation({ duration, iterations: -1 }) |
BPM脉搏呼吸灯 |
| 动态透明度 | backgroundOpacity |
焦点感知降级 |
| 窗口阴影 | setWindowShadow({ radius, color }) |
跨窗口光效联动 |
6.3 BPM阶段光效映射
| BPM阶段 | 速度范围 | 主题色 | 脉冲周期 | 光效强度 |
|---|---|---|---|---|
| 慢板 | <60 BPM | 深海蓝 #1E3A8A | 4秒 | 30% |
| 行板 | 60-90 BPM | 薄荷绿 #10B981 | 2秒 | 60% |
| 快板 | 90-120 BPM | 活力橙 #F59E0B | 1秒 | 80% |
| 急板 | >120 BPM | 炽烈红 #EF4444 | 0.5秒 | 100% |
6.4 智能体状态徽章动画
| 智能体状态 | 徽章颜色 | 动画效果 | 创作含义 |
|---|---|---|---|
| 空闲 | #888888 | 静态 | 智能体待命 |
| 思考中 | #8B5CF6 | 呼吸脉冲 | LLM生成旋律中 |
| 执行中 | #F97316 | 旋转进度 | 配器/混音处理中 |
| 已完成 | #10B981 | 静态 | 任务执行成功 |
| 异常 | #EF4444 | 快速闪烁 | 音频处理错误 |
6.5 性能优化建议
- 光效渲染优化:使用
willChange: true标记频繁变化的动画层,避免全量重绘 - 智能体并发控制:通过
maxConcurrentAgents: 4限制并发,防止资源争抢 - 频谱数据节流:FFT分析频率控制在20fps,避免过度计算
- 窗口管理:子窗口使用
TYPE_FLOAT类型,避免创建过多独立进程 - 音频缓冲:使用环形缓冲区管理音频数据,避免内存泄漏
七、总结与展望
本文基于HarmonyOS 6(API 23)的HMAF智能体框架、悬浮导航与沉浸光感特性,构建了一套面向PC端专业音乐制作的"音浪智脑"系统。核心创新点包括:
- 四层智能体协作架构:旋律、和声、混音、风格四大智能体通过HMAF的A2A模式实时协作,将创作者从重复性编曲工作中解放出来
- BPM脉搏光效系统:基于实时BPM与频谱能量的动态光效渲染,让创作者"看见"音乐的情绪色彩
- 悬浮创作导航:底部玻璃拟态导航承载四大模块,智能体状态徽章实时反馈,工程信息一键展开
- 多窗口光效联动:主窗口与三个浮动子窗口通过
WindowLightSync实现跨窗口光效同步,焦点感知自动调节 - 频谱能量光效:基于实时FFT频谱分析,低频触发深红光晕,中频触发暖黄光晕,高频触发冰蓝光晕
随着HarmonyOS生态的持续发展,音乐制作智能体将向着"数字人制作人"“多模态交互”"跨设备协作创作"等方向演进。HMAF框架的分布式能力与HarmonyOS的跨端协同特性,将为音乐创作带来"一机控全场、多屏协同作"的全新体验。
转载自:https://blog.csdn.net/u014727709/article/details/161265737
欢迎 👍点赞✍评论⭐收藏,欢迎指正
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)