当前位置: 首页 > news >正文

C++中的动态图形与音频同步:实现罗盘时钟与音乐播放器

目录

前言

一、项目概述

二、图形界面和时间圈的实现

2.1图形库的选择与图形系统初始化

2.2时间圈的动态更新

2.3多线程音乐播放

三、主函数实现

四、剩余代码解释


前言

时代滚滚向前,技术日新月异。作为身处信息时代的一份子,就需要不断加强学习,展现对技术的渴望,从基础学习逐步建立起属于自己的“知识大厦”,这样才能不被时代淘汰。

C++是一门强大的编程语言,以我的浅见看来,其高效和灵活的特点,能够更好的帮助开发人员的任务,满足开发需求,成为我们首选编程语言之一。因此,本文将探讨如何利用C++实现罗盘时钟,并且在时钟的滚动时循环播放设定好的音乐。

作为一名初学者,垦请各位大佬指出文中错误和提出建议,我将不胜感激!

一、项目概述

我的项目旨在创建一个时钟界面,既显示时间,又能够显示日期,在时钟显示的过程中,循环播放音乐,而且在这个界面中添加了背景图片,让结果呈现不至于枯燥乏味。

二、图形界面和时间圈的实现

2.1图形库的选择与图形系统初始化

我选择的是easyx.h图形库,然后利用initgraph函数初始化,设置屏幕高度和宽度。我设置的屏幕宽度为1080像素,高度为800像素,可以进行更改,你应当根据绘制时间圈的相关函数确定,否则呈现的效果不是很好。

#include<easyx.h>       //用于Windows平台的简易图形库,提供更高级的图形操作功能
//使用宏定义设置一个屏幕,宽度为1080像素,高度为800像素
#define Width 1080
#define Height 800
initgraph(Width, Height);               //初始化图像系统,根据定义的宽度和高度进行设置

2.2时间圈的动态更新

设置的时间圈有年、月、日、星期、时、分、秒七个,为了实现时间的平滑过渡,设计了一个TimeCircle结构体,包含了动画控制的参数。

//更新时间圈的函数,使用基于时间差的平滑过渡
void UpdateTimeCircle(TimeCircle& circle, int currentTime, double totalSegments)
{//检查当前时间是否到达了预定的更新时间if (circle.NextTime == currentTime){//计算时间差:当前时间与上一次更新的时间之差,通过totalSegments标准化//确保了时间差在0到1之间,便于后续计算角度增量double timeDelta = static_cast<double>(currentTime - circle.LastUpdateTime) / totalSegments;//更新LastUpdateTime,记录当前时间点,以便于下次计算时间差circle.LastUpdateTime = currentTime;//根据时间差计算角度增量:将时间差映射到角度变化,实现平滑过渡double angleIncrement = (2 * PI) * timeDelta;//更新时间圈的角度(Radian):累积角度增量circle.Radian += angleIncrement;//角度归一化:使用fmod函数确保角度在0到2π之间,防止超出一个圆周circle.Radian = fmod(circle.Radian, 2 * PI);}else{//当当前时间与NextTime不匹配时,重置时间圈的状态//这通常发生在时间跳变或初始化时circle.NextTime = currentTime;//将角度设置为0,准备从新时间点开始新的周期circle.Radian = 0;//更新LastUpdateTime到当前时间点,开始新的时间差计算周期circle.LastUpdateTime = currentTime;}
}

时间的绘制,采用了DrawCircle函数负责在指定坐标和参数下生成一个带有文本的圆,通过计算角度和调整文本位置确保正确显示时间。

void DrawCircle(TCHAR str[25], int variable, int fors, int R, double Radian, int maxRadius, int centerX, int centerY)
{//计算文本颜色//如果variable不为0,则根据当前时间变量计算HSL颜色,否则使用白色settextcolor(variable ? HSLtoRGB((360.f / fors) * variable, 1, 0.5f) : WHITE);//从左往右,每个变量是字体的设置分别为:大小、风格、名称、旋转角度、倾斜角度、宽度比例、是否使用粗体、是否使用斜体、是否使用下划线、字符集、输出精度、裁剪精度、抗锯齿质量、字符间距settextstyle(22, 0, L"微软雅黑", variable * 3600 / fors, variable * 3600 / fors, 0, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH);//计算角度,对于秒和分时间圈,使用更精确的更新double a = (fors == 60) ? ((variable + 0) * PI * 2 / fors - Radian) : (variable * PI * 2 / fors);//获取文本宽度和高度int w = textwidth(str);int h = textheight(str);//根据圆心、半径、角度,以及文本尺寸调整位置,确保文本居中int x = centerX + (R * cos(a)) - w / 2;int y = centerY - (R * sin(a)) - h / 2;//输出文本到计算的位置outtextxy(x, y, str);
}

2.3多线程音乐播放

为了实现音乐的循环播放,采用了多线程,一个线程独立负责音乐的播放,使用PlaySound函数,并通过std::this_thread::sleep_for精确控制播放的时间间隔,确保音乐无缝链接,但是我选择的音乐时间长短不一,因此为了使音乐完全播放,所以选择了最长音乐的时间长短上加一秒作为播放的间隔时间。

//音乐播放函数
void playMusic(const TCHAR* songs[], int totalSongs) {int songIndex = 0;while (true) {PlaySound(songs[songIndex], NULL, SND_FILENAME | SND_ASYNC);//精确控制播放间隔std::this_thread::sleep_for(std::chrono::seconds(165)); //假设每首歌的平均长度为2分钟45秒//更新 songIndex,并确保它在数组范围内循环songIndex = (songIndex + 1) % totalSongs;}
}

三、主函数实现

我就不再过多的解释了,我在代码中都有详细的注释。

//主函数
int main()
{//歌曲文件名数组const TCHAR* songs[4] = {TEXT(""),TEXT(""),TEXT(""),TEXT("")};const int totalSongs = 4; //歌曲总数//创建并启动音乐播放线程std::thread musicThread(playMusic, songs, totalSongs);initgraph(Width, Height);               //初始化图像系统,根据定义的宽度和高度进行设置cout << "Graphics initialized." << endl;   //图形初始化完成IMAGE back;       //加载背景图片loadimage(&back, _T("IMAGE"), _T("back"), 1080, 800);       //按照设定的宽度和高度铺满设定的屏幕putimage(0, 0, &back);           //设定绘制图像开始的坐标,表示从(0,0)开始绘制cout << "Background image loaded." << endl;    //背景图片加载完成SYSTEMTIME ti;TimeCircle TC[7];//7表示对应年、月、日、星期、时、分、秒TCHAR str[25];   //能够显示的最长文本,比如2023年10月29号周日20时45分29秒+一个休止符//设置时钟的属性for (int i = 0; i < 7; i++){TC[i].R = (i + 1) * 50;//按照每次增加50像素,来确定每个时间圈的半径TC[i].Radian = 0;  //设置初始弧度为0TC[i].NextTime = 0;//时间圈设置其分割数switch (i){case 0:TC[i].fors = 1; break;							case 1:TC[i].fors = 12; break;					case 2:TC[i].fors = 30; break;							case 3:TC[i].fors = 7; break;							case 4:TC[i].fors = 24; break;							case 5:TC[i].fors = 60; break;							case 6:TC[i].fors = 60; break;							}}BeginBatchDraw();  //批量绘制while (true){GetLocalTime(&ti);   //获取本地系统时间TC[2].fors = monthdasy(ti.wYear, ti.wMonth);  //基于当前的年和月,计算日时间圈的天数//更新时间圈for (int j = 0; j < 7; j++) {int currentTime = 0; //默认值为0,用于未明确指定的情况if (j == 0) {//年时间圈currentTime = 0; //通常年时间圈不会像其他圈一样根据实时更新,这里保持默认值0}else if (j == 1) {//月时间圈currentTime = ti.wMonth;}else if (j == 2) {//日时间圈currentTime = ti.wDay;}else if (j == 3) {//周时间圈currentTime = ti.wDayOfWeek;}else if (j == 4) {//时时间圈currentTime = ti.wHour;}else if (j == 5) {//分时间圈currentTime = ti.wMinute;}else if (j == 6) {//秒时间圈currentTime = ti.wSecond;}UpdateTimeCircle(TC[j], currentTime, TC[j].fors);}//绘制时间圈for (int j = 0; j < 7; j++) {    //遍历所有7个时间圈(年、月、日、周、时、分、秒)//确保str是一个wchar_t*类型的指针wchar_t str[25]; //确保了时间圈文本的完整性和正确性for (int i = 0; i < TC[j].fors; i++) {//声明并初始化所有可能在 switch 语句中使用的局部变量int month = 0;int day = 0;switch (j) {case 0: //年时间圈_stprintf_s(str, _T("%d年"), ti.wYear);break;case 1: //月时间圈month = (i + ti.wMonth - 1) % 12 + 1;_stprintf_s(str, _T("%02d月"), month);break;case 2: //日时间圈day = (i + ti.wDay - 1) % TC[2].fors + 1;_stprintf_s(str, _T("%02d号"), day);break;case 3: //周时间圈//初始化str为"周",然后追加星期字符swprintf_s(str, L"周%c", L"日一二三四五六"[(i + ti.wDayOfWeek) % 7]);break;case 4: //时时间圈_stprintf_s(str, _T("%02d时"), (i + ti.wHour) % 24);break;case 5: //分时间圈_stprintf_s(str, _T("%02d分"), (i + ti.wMinute) % 60);break;case 6: //秒时间圈_stprintf_s(str, _T("%02d秒"), (i + ti.wSecond) % 60);break;}DrawCircle(str, i, TC[j].fors, TC[j].R, TC[j].Radian, TC[6].R, Width / 2, Height / 2);//调用DrawCircle函数绘制当前时间圈的文本}}FlushBatchDraw();  //批处理绘制,确保所有绘制操作一次性完成,提高绘制效率}return 0;
}

四、剩余代码解释

#include <math.h>       //数学函数库,用于数学计算
#include <time.h>       //时间处理库,提供系统时间的获取、时间格式化等函数
#include<iostream>      //C++标准输入输出流库,提供高级的输入输出操作
#include <Windows.h>    //包含Windows API的函数,提供Windows系统下的各种功能
#include <chrono>       //包含C++标准库中的时间处理功能
#include <thread>       //提供多线程支持
using namespace std;              //引入标准命名空间std,以便于直接使用cout,cin等标准库函数,而不需要std::前缀
const double PI = 4 * atan(1.0);   //利用反正切函数atan计算Π(圆周率),以便后面时钟的展开
int monthdasy(int y, int m);
//monthdasy    计算给定年份和月份的天数
//y   确定某年是否为闰年,进而判断二月天数
//m   确定某月的天数
//定义TimeCircle,用于储存与时间有关的圆形元素信息
struct TimeCircle
{int fors;	       //圆形被分割的份数,用于图形分割										int R;			   //圆的半径									double NextTime;   //用于动画控制,确保图形在特定时间点更新或重绘									double Radian;	   //当前的弧度值,用于计算圆上的点的坐标	int LastUpdateTime;//上次更新的时间点};
int monthdasy(int y, int m)
{if (m == 2)return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) ? 29 : 28;elsereturn 31 - (m - 3) % 5 % 2;
}
//计算给定月份的天数,如果年份是闰年,则2月是29天,反之28天,return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) ? 29 : 28;
//如果给定年份是非整百的,则能够被4整除,不能被100整除则是闰年,反之被400整除则是闰年
//如果计算的不是2月的,那么则根据return 31 - (m - 3) % 5 % 2;
//除去2月,剩下的有1、3、4、5、6、7、8、9、10、11、12月,当m等于4时,4-3等于1,然后1%5,取余为1,1%2,取余为1,则4月的天数为30天。
//取余运算,当被除数小于除数时,取余就是被除数本身


http://www.mrgr.cn/news/37207.html

相关文章:

  • Flask 本地测试完成,如何部署到网络上,买什么样的空间
  • HarmonyOS异常处理实践
  • VC++同时处理ANSI和Unicode字符集,除了使用TCHAR和_T()宏外,还有其他方法可以实现吗?
  • 基于51单片机的方向盘模拟系统
  • 【学习笔记】手写 Tomcat 七
  • 算法学习021 c++有多少张桌子 并查集算法学习 中小学算法思维学习 比赛算法题解 信奥算法解析
  • TMR技术的发展及其应用技术的介绍
  • PDF 秒变 JPG,2024 这些工具来助力
  • 2024四川省赛 The 2024 Sichuan Provincial Collegiate Programming Contest补题记录
  • Java | Leetcode Java题解之第440题字典序的第K小数字
  • 增量式编码器实现原理
  • Materials - 基础视差原理
  • sysbench 命令:跨平台的基准测试工具
  • 秒懂Linux之信号
  • PSS-sdy_opengl_sdd
  • 将查询的数据库信息存入session,反复使用的方法是否可以
  • windows C++-管理计划程序实例
  • Meta宣布为Ray-Ban Meta智能眼镜增加全新AI功能
  • 2024引领视频剪辑潮流的专业工具
  • NASA:ATLAS/ICESat-2 L3 A沿线内陆地表水数据V006数据集