这是一个将指数ETF基金数据转换为 MIDI音乐的 python程序示例。程序使用 Tushare 获取股票数据,将收盘价映射为 MIDI音符的音高,并且根据交易量控制音量的大小
pip install tushare
python 3.7 tushare==1.2.89
pip install midiutil
MIDIUtil==1.2.1
编写 stock2mid.py 如下
# -*- coding: utf-8 -*-
"""
使用 Tushare 获取股票数据并转换为 MIDI文件
需要先安装依赖:
pip install tushare midiutil
"""
import sys
import tushare as ts
from midiutil import MIDIFile
if len(sys.argv) ==2:
code = sys.argv[1]
else:
print('usage: python stock2mid.py stockcode ')
sys.exit(1)
if len(code) !=6:
print('stock code length: 6')
sys.exit(2)
df = ts.get_k_data(code)
if df.empty is True:
print(" df is empty ")
sys.exit(2)
df = df[ df['date'] > '2024-01-01']
if len(df) <60:
print(len(df)," < 60")
sys.exit(2)
print('len(df)=', len(df))
# DataFrame 重建索引
df = df.reset_index(drop=True)
# 提取收盘价序列
prices = df['close'].astype(float).tolist()
# 提取交易量序列
volumes = df['volume'].astype(int).tolist()
max_vol = max(volumes)
# 根据交易量控制音量的大小:将交易量转为音量
vols = [ int(100*i/max_vol)+20 for i in volumes ]
# 使用 zip 函数组合 prices 和 vols 列表
combined = list(zip(prices, vols))
print(combined[0:5])
# 数据归一化处理
min_price = min(prices)
max_price = max(prices)
price_range = max_price - min_price
print(f"{code}: max={max_price}, min={min_price}, range={price_range}")
print('max/min:', max_price/min_price)
# MIDI参数配置
# C3(MIDI音符范围最小值=48)
min_note = 48
# C5(MIDI音符范围最大值=84)
max_note = 84
if (max_price/min_price) < (84.0/48):
max_note = int(50 * max_price/min_price)
if max_note %2 ==1:
max_note += 1
print(f"max_note={max_note}, min_note={min_note} ")
print('max_note/50=', max_note/50)
track = 0
channel = 0
tempo = 120 # BPM
volume = 100 # 音量(0-127)
# 创建MIDI文件
midi = MIDIFile(1) # 单音轨
midi.addTrackName(track, 0, f"Stock {code}")
midi.addTempo(track, 0, tempo)
# 生成音符序列
time_counter = 0 # 时间指针(单位:拍)
for price,vol in combined:
if price_range < 0.01: # 处理价格无波动的情况
pitch = (max_note + min_note) // 2
else:
normalized = (price - min_price) / price_range
pitch = min_note + int(normalized * (max_note - min_note))
# 添加音符(每个音符持续1拍,间隔1拍)
midi.addNote(track, channel, pitch, time_counter, 1, vol)
time_counter += 1
# 保存MIDI文件
output_file =f"melo_{code}.mid"
with open(output_file, 'wb') as f:
midi.writeFile(f)
print(f" 生成:{output_file}")
以 华泰柏瑞--沪深300ETF (510300)为示例:
运行 python stock2mid.py 510300
生成 melo_510300.mid
编写 play_mid.py 如下
# -*- coding: utf-8 -*-
import os
import sys
import time
from tkinter import filedialog
import traceback
import pygame
from pygame import mixer
def mixer_init():
freq = 44100
bitsize = -16
channels = 2
buffer = 2048
mixer.init(freq, bitsize, channels, buffer)
# optional volume 0 to 1.0
mixer.music.set_volume(0.9)
def play_mid(file):
if mixer.music.get_busy():
mixer.music.fadeout(1000)
mixer.music.stop()
clock = pygame.time.Clock()
try:
mixer.music.load(file)
except:
print(traceback.format_exc())
mixer.music.play()
while mixer.music.get_busy():
clock.tick(30)
# main()
f1 = ''
if len(sys.argv) ==1:
filetypes = [('mid file','.mid'),('mp3 file','.mp3'),('wav file','.wav')]
f1 = filedialog.askopenfilename(initialdir='D:/Music', filetypes=filetypes)
elif len(sys.argv) ==2:
f1 = sys.argv[1]
else:
print('usage: python play_mid.py file1.mid')
print('usage: python play_mid.py file1.mp3')
print('usage: python play_mid.py file1.wav')
sys.exit(1)
if not os.path.exists(f1):
print(f"{f1} is not exists.")
sys.exit(2)
fn,ext = os.path.splitext(f1)
if ext.lower() not in ('.mid','.mp3','.wav'):
print('.ext is not (.mid , .mp3','.wav')
sys.exit(2)
print(f1)
mixer_init()
time1 = time.time()
try:
play_mid(f1)
except KeyboardInterrupt as ex:
# if user hits Ctrl+C then exit
# (works only in console mode)
mixer.music.fadeout(1000)
mixer.music.stop()
raise SystemExit from ex
mixer.music.stop()
time2 = time.time()
print("run time: %.3f s" % (time2-time1))
运行 python play_mid.py melo_510300.mid

被折叠的 条评论
为什么被折叠?



