import time
import random
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.textinput import TextInput
from kivy.uix.spinner import Spinner
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, BooleanProperty
from kivy.animation import Animation
from kivy_garden.graph import Graph, MeshLinePlot
from kivy.uix.popup import Popup
from kivy.uix.progressbar import ProgressBar
class InitialScreen(Screen):
"""初始界面:设置音乐参数"""
def __init__(self, **kwargs):
super(InitialScreen, self).__init__(**kwargs)
# 创建主布局
layout = BoxLayout(orientation='vertical', padding=20, spacing=15)
# 添加标题
title = Label(
text="音乐创作助手",
font_size=32,
size_hint=(1, 0.1)
)
layout.add_widget(title)
# 添加节拍选择
layout.add_widget(Label(text="节拍:", size_hint=(1, 0.05)))
self.beats_spinner = Spinner(
text='请选择',
values=('2/4', '3/4', '4/4', '6/8'),
size_hint=(1, 0.08)
)
layout.add_widget(self.beats_spinner)
# 添加音速控制
layout.add_widget(Label(text="音速 (BPM):", size_hint=(1, 0.05)))
self.tempo_slider = Slider(
min=60,
max=200,
value=120,
step=1,
size_hint=(1, 0.1)
)
self.tempo_value = Label(
text=f"{int(self.tempo_slider.value)} BPM",
size_hint=(1, 0.05)
)
self.tempo_slider.bind(value=lambda instance, value: setattr(self.tempo_value, 'text', f"{int(value)} BPM"))
layout.add_widget(self.tempo_slider)
layout.add_widget(self.tempo_value)
# 添加音调选择
layout.add_widget(Label(text="音调:", size_hint=(1, 0.05)))
self.pitch_spinner = Spinner(
text='请选择',
values=('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G'),
size_hint=(1, 0.08)
)
layout.add_widget(self.pitch_spinner)
# 添加风格选择
layout.add_widget(Label(text="风格:", size_hint=(1, 0.05)))
self.style_spinner = Spinner(
text='请选择',
values=('Pop', 'Rock', 'Jazz', 'Classical', 'Electronic', 'Folk', 'Blues', 'Country'),
size_hint=(1, 0.08)
)
layout.add_widget(self.style_spinner)
# 添加伴奏音型选择
layout.add_widget(Label(text="伴奏音型:", size_hint=(1, 0.05)))
self.accompaniment_spinner = Spinner(
text='请选择',
values=('Piano', 'Guitar', 'Strings', 'Drums', 'Bass', 'Synth', 'Choir', 'Orchestra'),
size_hint=(1, 0.08)
)
layout.add_widget(self.accompaniment_spinner)
# 添加旋律音高序列输入
layout.add_widget(Label(text="旋律音高序列:", size_hint=(1, 0.05)))
self.pitch_sequence = TextInput(
multiline=False,
hint_text="例如: C4 D4 E4 F4 G4",
size_hint=(1, 0.08)
)
layout.add_widget(self.pitch_sequence)
# 添加开始录制按钮
self.start_button = Button(
text="开始录制",
font_size=24,
size_hint=(1, 0.15),
background_color=(0.2, 0.6, 0.9, 1)
)
self.start_button.bind(on_press=self.validate_and_start)
layout.add_widget(self.start_button)
self.add_widget(layout)
def validate_and_start(self, instance):
"""验证表单并切换到录音界面"""
if (self.beats_spinner.text != '请选择' and
self.pitch_spinner.text != '请选择' and
self.style_spinner.text != '请选择' and
self.accompaniment_spinner.text != '请选择' and
self.pitch_sequence.text.strip()):
# 获取当前选中的值
app = App.get_running_app()
app.beats = self.beats_spinner.text
app.tempo = int(self.tempo_slider.value)
app.pitch = self.pitch_spinner.text
app.style = self.style_spinner.text
app.accompaniment = self.accompaniment_spinner.text
app.pitch_sequence = self.pitch_sequence.text
# 切换到录音界面
self.manager.current = 'recording'
else:
# 显示错误提示
self.show_error("请完成所有参数选择")
def show_error(self, message):
"""显示错误提示"""
# 简单实现:改变按钮颜色
self.start_button.background_color = (0.9, 0.2, 0.2, 1)
Clock.schedule_once(lambda dt: setattr(self.start_button, 'background_color', (0.2, 0.6, 0.9, 1)), 1)
class RecordingScreen(Screen):
"""录音界面:控制录音过程"""
def __init__(self, **kwargs):
super(RecordingScreen, self).__init__(**kwargs)
# 录音状态
self.is_recording = False
self.is_paused = False
self.is_playing = False
self.recording_time = 0
self.timer_event = None
self.recording_data = [] # 存储录音数据
# 创建主布局
layout = BoxLayout(orientation='vertical', padding=20, spacing=15)
# 返回按钮
back_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.05))
back_button = Button(
text="返回",
size_hint=(0.2, 1),
background_color=(0.6, 0.6, 0.6, 1)
)
back_button.bind(on_press=self.go_back)
back_layout.add_widget(back_button)
layout.add_widget(back_layout)
# 添加标题
title = Label(
text="正在录音",
font_size=32,
size_hint=(1, 0.1)
)
layout.add_widget(title)
# 参数显示区域
self.params_layout = BoxLayout(orientation='vertical', size_hint=(1, 0.25))
layout.add_widget(self.params_layout)
# 录音时间显示
self.time_label = Label(
text="00:00",
font_size=48,
size_hint=(1, 0.1)
)
layout.add_widget(self.time_label)
# 波形可视化
self.graph = Graph(
xlabel='时间',
ylabel='振幅',
x_ticks_minor=5,
x_ticks_major=25,
y_ticks_major=1,
y_grid_label=True,
x_grid_label=True,
padding=5,
x_grid=True,
y_grid=True,
xmin=0,
xmax=100,
ymin=-1,
ymax=1,
size_hint=(1, 0.25)
)
self.plot = MeshLinePlot(color=[0.2, 0.6, 0.9, 1])
self.graph.add_plot(self.plot)
layout.add_widget(self.graph)
# 控制按钮区域
control_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.15), spacing=10)
# 录音/停止按钮
self.record_button = Button(
text="开始",
font_size=24,
background_color=(0.2, 0.8, 0.2, 1)
)
self.record_button.bind(on_press=self.toggle_recording)
control_layout.add_widget(self.record_button)
# 生成按钮
self.generate_button = Button(
text="生成",
font_size=24,
background_color=(0.8, 0.6, 0.2, 1),
disabled=True
)
self.generate_button.bind(on_press=self.generate_music)
control_layout.add_widget(self.generate_button)
layout.add_widget(control_layout)
# 功能按钮区域
function_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.15), spacing=10)
# 暂停/继续按钮
self.pause_button = Button(
text="暂停",
font_size=24,
background_color=(0.8, 0.8, 0.2, 1),
disabled=True
)
self.pause_button.bind(on_press=self.toggle_pause)
function_layout.add_widget(self.pause_button)
# 播放/暂停按钮
self.play_button = Button(
text="播放",
font_size=24,
background_color=(0.2, 0.6, 0.9, 1),
disabled=True
)
self.play_button.bind(on_press=self.toggle_play)
function_layout.add_widget(self.play_button)
layout.add_widget(function_layout)
# 下载按钮
self.download_button = Button(
text="下载",
font_size=24,
size_hint=(1, 0.1),
background_color=(0.6, 0.2, 0.8, 1),
disabled=True
)
self.download_button.bind(on_press=self.download_recording)
layout.add_widget(self.download_button)
self.add_widget(layout)
def on_enter(self):
"""进入屏幕时更新参数显示"""
app = App.get_running_app()
# 清空参数布局
self.params_layout.clear_widgets()
# 添加参数显示
params = [
f"节拍: {app.beats}",
f"音速: {app.tempo} BPM",
f"音调: {app.pitch}",
f"风格: {app.style}",
f"伴奏: {app.accompaniment}",
f"旋律音高: {app.pitch_sequence}"
]
for param in params:
label = Label(
text=param,
font_size=18,
size_hint=(1, 0.2),
halign='left'
)
self.params_layout.add_widget(label)
def toggle_recording(self, instance):
"""开始/停止录音"""
if not self.is_recording:
# 开始录音
self.is_recording = True
self.record_button.text = "停止"
self.record_button.background_color = (0.8, 0.2, 0.2, 1)
self.pause_button.disabled = False
self.play_button.disabled = True
self.download_button.disabled = True
self.generate_button.disabled = True
# 重置录音数据
self.recording_data = []
self.plot.points = []
# 启动计时器
self.recording_time = 0
self.update_time()
self.timer_event = Clock.schedule_interval(self.update_time, 0.1) # 100ms更新一次
# 显示录音中提示
app = App.get_running_app()
app.show_message("开始录音...")
else:
# 停止录音
self.is_recording = False
self.record_button.text = "开始"
self.record_button.background_color = (0.2, 0.8, 0.2, 1)
self.pause_button.disabled = True
self.play_button.disabled = False
self.download_button.disabled = False
self.generate_button.disabled = False
# 停止计时器
if self.timer_event:
self.timer_event.cancel()
# 显示录音完成提示
app = App.get_running_app()
app.show_message(f"录音完成,时长: {self.recording_time//10}秒")
def update_time(self, dt=None):
"""更新录音时间和波形数据"""
self.recording_time += 1
seconds = self.recording_time // 10 # 转换为秒
tenths = self.recording_time % 10 # 十分之一秒
self.time_label.text = f"{seconds:02d}:{tenths:01d}"
# 生成随机波形数据(实际应用中会从麦克风获取)
if self.is_recording and not self.is_paused:
x = len(self.recording_data)
y = random.uniform(-0.8, 0.8)
self.recording_data.append((x, y))
# 更新波形显示
if len(self.recording_data) > 100:
self.plot.points = self.recording_data[-100:]
else:
self.plot.points = self.recording_data
def toggle_pause(self, instance):
"""暂停/继续录音"""
if not self.is_paused:
# 暂停录音
self.is_paused = True
self.pause_button.text = "继续"
if self.timer_event:
self.timer_event.cancel()
else:
# 继续录音
self.is_paused = False
self.pause_button.text = "暂停"
self.timer_event = Clock.schedule_interval(self.update_time, 0.1)
def toggle_play(self, instance):
"""播放/暂停录音"""
if not self.is_playing:
# 开始播放
self.is_playing = True
self.play_button.text = "暂停"
# 模拟播放波形动画
self.play_index = 0
self.play_timer = Clock.schedule_interval(self.update_play, 0.1)
else:
# 暂停播放
self.is_playing = False
self.play_button.text = "播放"
if hasattr(self, 'play_timer'):
self.play_timer.cancel()
def update_play(self, dt):
"""更新播放时的波形显示"""
if not self.recording_data:
return
self.play_index += 1
if self.play_index >= len(self.recording_data):
self.play_index = 0
self.is_playing = False
self.play_button.text = "播放"
self.play_timer.cancel()
return
# 更新波形显示
start = max(0, self.play_index - 50)
end = self.play_index + 50
if end > len(self.recording_data):
end = len(self.recording_data)
start = max(0, end - 100)
self.plot.points = self.recording_data[start:end]
def generate_music(self, instance):
"""生成音乐"""
if not self.is_recording:
# 显示生成中对话框
self.progress_popup = Popup(
title="生成音乐",
size_hint=(None, None),
size=(400, 200)
)
progress_layout = BoxLayout(orientation='vertical', padding=20)
progress_label = Label(text="正在生成音乐...")
progress_bar = ProgressBar(max=100, value=0)
progress_layout.add_widget(progress_label)
progress_layout.add_widget(progress_bar)
self.progress_popup.content = progress_layout
self.progress_popup.open()
# 模拟生成过程
self.progress = 0
self.generate_timer = Clock.schedule_interval(lambda dt: self.update_progress(progress_bar, progress_label), 0.1)
else:
# 提示用户先停止录音
app = App.get_running_app()
app.show_message("请先停止录音")
def update_progress(self, progress_bar, progress_label):
"""更新生成进度"""
self.progress += 1
progress_bar.value = self.progress
progress_label.text = f"正在生成音乐... {self.progress}%"
if self.progress >= 100:
self.generate_timer.cancel()
self.progress_popup.dismiss()
# 显示成功提示
app = App.get_running_app()
app.show_message("音乐生成成功")
def download_recording(self, instance):
"""下载录音"""
# 显示下载中提示
app = App.get_running_app()
app.show_message("准备下载...")
# 模拟下载过程
Clock.schedule_once(self.on_download_complete, 2)
def on_download_complete(self, dt):
"""下载完成回调"""
# 显示成功提示
app = App.get_running_app()
app.show_message("下载完成")
def go_back(self, instance):
"""返回初始界面"""
# 停止所有活动
if self.is_recording:
self.toggle_recording(None)
if hasattr(self, 'play_timer') and self.play_timer.is_triggered:
self.play_timer.cancel()
# 返回初始界面
self.manager.current = 'initial'
class MusicRecorderApp(App):
"""音乐录音应用主类"""
beats = StringProperty("")
tempo = NumericProperty(120)
pitch = StringProperty("")
style = StringProperty("")
accompaniment = StringProperty("")
pitch_sequence = StringProperty("")
def build(self):
"""构建应用UI"""
# 创建屏幕管理器
sm = ScreenManager()
# 添加屏幕
sm.add_widget(InitialScreen(name='initial'))
sm.add_widget(RecordingScreen(name='recording'))
return sm
def show_message(self, message):
"""显示消息提示"""
popup = Popup(
title='提示',
content=Label(text=message),
size_hint=(None, None),
size=(300, 200),
auto_dismiss=True
)
popup.open()
if __name__ == '__main__':
# 启动应用
MusicRecorderApp().run()