# flutter_piano
使用 Flutter.io 构建的跨平台 MIDI 钢琴。
- 此应用程序可在 iOS 和 Android 上运行。
- 此应用运行我为 Flutter 插件
flutter_midi构建的自定义跨平台 MIDI 合成器,该插件使用 .SF2 音色库文件。
assets:
- assets/sounds/Piano.SF2
- 已包含针对视障人士的语义。所有琴键都显示为按钮,并带有 MIDI 音符的音高名称,而不仅仅是数字。
入门
此应用程序仅在横屏模式下运行,方向在 AndroidManifest.xml 和 Runner.xcworspace 设置中设置。
- 请确保将音量调高并取消静音(应用程序会尝试取消设备静音,但可能会被覆盖)。
- 点击任意音符即可弹奏
- 向任一方向滚动即可更改八度
- 支持多指复音
配置
-
还可以选择在设置中更改琴键宽度,以调整琴键密度。
-
如果想要更简约的外观,还可以关闭琴键标签。
iOS
Android

- 您可以将 Piano.sf2 文件更改为任何音色库文件,以播放不同的乐器。
截图
iOS
Android


代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_midi/flutter_midi.dart';
import 'package:tonic/tonic.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
initState() {
FlutterMidi.unmute();
rootBundle.load("assets/sounds/Piano.SF2").then((sf2) {
FlutterMidi.prepare(sf2: sf2, name: "Piano.SF2");
});
super.initState();
}
double get keyWidth => 80 + (80 * _widthRatio);
double _widthRatio = 0.0;
bool _showLabels = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'The Pocket Piano',
theme: ThemeData.dark(),
home: Scaffold(
drawer: Drawer(
child: SafeArea(
child: ListView(children: <Widget>[
Container(height: 20.0),
ListTile(title: Text("Change Width")),
Slider(
activeColor: Colors.redAccent,
inactiveColor: Colors.white,
min: 0.0,
max: 1.0,
value: _widthRatio,
onChanged: (double value) =>
setState(() => _widthRatio = value)),
Divider(),
ListTile(
title: Text("Show Labels"),
trailing: Switch(
value: _showLabels,
onChanged: (bool value) =>
setState(() => _showLabels = value))),
Divider(),
]))),
appBar: AppBar(title: Text("The Pocket Piano")),
body: ListView.builder(
itemCount: 7,
controller: ScrollController(initialScrollOffset: 1500.0),
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
final int i = index * 12;
return SafeArea(
child: Stack(children: <Widget>[
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
_buildKey(24 + i, false),
_buildKey(26 + i, false),
_buildKey(28 + i, false),
_buildKey(29 + i, false),
_buildKey(31 + i, false),
_buildKey(33 + i, false),
_buildKey(35 + i, false),
]),
Positioned(
left: 0.0,
right: 0.0,
bottom: 100,
top: 0.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(width: keyWidth * .5),
_buildKey(25 + i, true),
_buildKey(27 + i, true),
Container(width: keyWidth),
_buildKey(30 + i, true),
_buildKey(32 + i, true),
_buildKey(34 + i, true),
Container(width: keyWidth * .5),
])),
]),
);
},
)),
);
}
Widget _buildKey(int midi, bool accidental) {
final pitchName = Pitch.fromMidiNumber(midi).toString();
final pianoKey = Stack(
children: <Widget>[
Semantics(
button: true,
hint: pitchName,
child: Material(
borderRadius: borderRadius,
color: accidental ? Colors.black : Colors.white,
child: InkWell(
borderRadius: borderRadius,
highlightColor: Colors.grey,
onTap: () {},
onTapDown: (_) => FlutterMidi.playMidiNote(midi: midi),
))),
Positioned(
left: 0.0,
right: 0.0,
bottom: 20.0,
child: _showLabels
? Text(pitchName,
textAlign: TextAlign.center,
style: TextStyle(
color: !accidental ? Colors.black : Colors.white))
: Container()),
],
);
if (accidental) {
return Container(
width: keyWidth,
margin: EdgeInsets.symmetric(horizontal: 2.0),
padding: EdgeInsets.symmetric(horizontal: keyWidth * .1),
child: Material(
elevation: 6.0,
borderRadius: borderRadius,
shadowColor: Color(0x802196F3),
child: pianoKey));
}
return Container(
width: keyWidth,
child: pianoKey,
margin: EdgeInsets.symmetric(horizontal: 2.0));
}
}
const BorderRadiusGeometry borderRadius = BorderRadius.only(
bottomLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0));