安卓版简谱播放器midi16JPBFQ-ZXQMQZQ软件代码

package com.example.miditest;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.billthefarmer.mididriver.MidiDriver;

public class MainActivity extends AppCompatActivity implements MidiDriver.OnMidiStartListener {

    private MidiDriver midiDriver;
    private byte[] event;
    private int[] config;
    private Button buttonExport;
    private FrameLayout trackContainer;
    private GridView trackButtonsGrid;
    private Button buttonPlay;
    private Button buttonStop;
    private CheckBox checkBoxLoop;

    private Handler handler;
    private boolean isPlaying = false;
    private boolean shouldLoop = false;
    private List<List<NoteEvent>> allTrackEvents = new ArrayList<>();
    private int[] currentNoteIndices = new int[16];
    private EditText[] trackEditTexts = new EditText[16];
    private List<Integer> trackTempos = new ArrayList<>();
    private List<String> trackKeys = new ArrayList<>();
    private List<Runnable> trackRunnables = new ArrayList<>();

    // 默认参数
    private int defaultTempo = 120;
    private int defaultInstrument = 0;
    private String defaultKey = "C大调";

    // 调式映射表
    private static final HashMap<String, Integer> KEY_SIGNATURES = new HashMap<String, Integer>() {{
        // 大调
        put("C大调", 0); put("C", 0);
        put("G大调", 7); put("G", 7);
        put("D大调", 2); put("D", 2);
        put("A大调", -3); put("A", -3);
        put("E大调", 4); put("E", 4);
        put("B大调", -1); put("B", -1);
        put("F#大调", 6); put("F#", 6); put("F♯大调", 6); put("F♯", 6);
        put("C#大调", 1); put("C#", 1); put("C♯大调", 1); put("C♯", 1);
        put("F大调", -5); put("F", -5);
        put("降B大调", -2); put("Bb大调", -2); put("降B", -2); put("Bb", -2); put("B♭大调", -2); put("B♭", -2);
        put("降E大调", -4); put("Eb大调", -4); put("降E", -4); put("Eb", -4); put("E♭大调", -4); put("E♭", -4);
        put("降A大调", 3); put("Ab大调", 3); put("降A", 3); put("Ab", 3); put("A♭大调", 3); put("A♭", 3);
        put("降D大调", 5); put("Db大调", 5); put("降D", 5); put("Db", 5); put("D♭大调", 5); put("D♭", 5);
        put("降G大调", -6); put("Gb大调", -6); put("降G", -6); put("Gb", -6); put("G♭大调", -6); put("G♭", -6);

        // 小调(相对小调)
        put("A小调", 0); put("Am", 0); put("a小调", 0); put("am", 0);
        put("E小调", 7); put("Em", 7); put("e小调", 7); put("em", 7);
        put("B小调", 2); put("Bm", 2); put("b小调", 2); put("bm", 2);
        put("F#小调", -3); put("F#m", -3); put("f#小调", -3); put("f#m", -3); put("F♯小调", -3); put("F♯m", -3);
        put("C#小调", 4); put("C#m", 4); put("c#小调", 4); put("c#m", 4); put("C♯小调", 4); put("C♯m", 4);
        put("G#小调", -1); put("G#m", -1); put("g#小调", -1); put("g#m", -1); put("G♯小调", -1); put("G♯m", -1);
        put("D#小调", 6); put("D#m", 6); put("d#小调", 6); put("d#m", 6); put("D♯小调", 6); put("D♯m", 6);
        put("A#小调", 1); put("A#m", 1); put("a#小调", 1); put("a#m", 1); put("A♯小调", 1); put("A♯m", 1);
        put("D小调", -5); put("Dm", -5); put("d小调", -5); put("dm", -5);
        put("G小调", -2); put("Gm", -2); put("g小调", -2); put("gm", -2);
        put("C小调", -4); put("Cm", -4); put("c小调", -4); put("cm", -4);
        put("F小调", 3); put("Fm", 3); put("f小调", 3); put("fm", 3);
        put("降B小调", 5); put("Bbm", 5); put("降Bm", 5); put("bb小调", 5); put("bbm", 5); put("B♭小调", 5); put("B♭m", 5);
        put("降E小调", -6); put("Ebm", -6); put("降Em", -6); put("eb小调", -6); put("ebm", -6); put("E♭小调", -6); put("E♭m", -6);
    }};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化视图
        trackContainer = findViewById(R.id.trackContainer);
        trackButtonsGrid = findViewById(R.id.trackButtonsGrid);
        buttonPlay = findViewById(R.id.buttonPlay);
        buttonStop = findViewById(R.id.buttonStop);
        buttonExport = findViewById(R.id.buttonExport);
        checkBoxLoop = findViewById(R.id.checkBoxLoop);

        // 设置导出按钮点击监听
        buttonExport.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exportToMidi();
            }
        });

        // 初始化16个音轨文本框
        initTrackEditTexts();

        // 初始化音轨选择按钮
        initTrackButtons();

        // 默认显示第一个音轨
        trackEditTexts[0].setVisibility(View.VISIBLE);
        for (int i = 1; i < 16; i++) {
            trackEditTexts[i].setVisibility(View.GONE);
        }

        // 设置按钮点击监听
        buttonPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startPlaying();
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopPlaying();
            }
        });

        checkBoxLoop.setOnCheckedChangeListener((buttonView, isChecked) -> {
            shouldLoop = isChecked;
        });

        // 初始化Handler
        handler = new Handler(Looper.getMainLooper());

        // 实例化MIDI驱动
        midiDriver = MidiDriver.getInstance();
        midiDriver.setOnMidiStartListener(this);
    }

    private void initTrackEditTexts() {
        // 第一个音轨文本框已经存在,获取引用
        trackEditTexts[0] = findViewById(R.id.editTextTrack1);

        // 创建其他15个音轨文本框,放在同一个位置
        for (int i = 1; i < 16; i++) {
            EditText editText = new EditText(this);
            editText.setId(View.generateViewId());

            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.MATCH_PARENT
            );
            editText.setLayoutParams(params);

            editText.setHint("音轨" + (i + 1) + ": [音色,速度,调式]简谱内容");
            editText.setInputType(android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE);
            editText.setMinLines(3);
            editText.setVerticalScrollBarEnabled(true);
            editText.setVisibility(View.GONE);

            trackContainer.addView(editText);
            trackEditTexts[i] = editText;
        }
    }

    private void initTrackButtons() {
        List<HashMap<String, String>> data = new ArrayList<>();

        for (int i = 0; i < 16; i++) {
            HashMap<String, String> map = new HashMap<>();
            map.put("text", "音轨" + (i + 1));
            data.add(map);
        }

        String[] from = {"text"};
        int[] to = {android.R.id.text1};

        SimpleAdapter adapter = new SimpleAdapter(this, data,
                android.R.layout.simple_list_item_1, from, to);

        trackButtonsGrid.setAdapter(adapter);
        trackButtonsGrid.setOnItemClickListener((parent, view, position, id) -> {
            toggleTrackVisibility(position);
        });
    }

    private void toggleTrackVisibility(int trackIndex) {
        if (trackIndex >= 0 && trackIndex < 16) {
            for (int i = 0; i < 16; i++) {
                trackEditTexts[i].setVisibility(View.GONE);
            }
            trackEditTexts[trackIndex].setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        midiDriver.start();
        config = midiDriver.config();
    }

    @Override
    protected void onPause() {
        super.onPause();
        stopPlaying();
        midiDriver.stop();
    }

    @Override
    public void onMidiStart() {
        Log.d("MidiTest", "MIDI started");
    }

    public void startPlaying() {
        if (isPlaying) {
            return;
        }

        // 解析所有音轨
        allTrackEvents.clear();
        trackTempos.clear();
        trackKeys.clear();
        trackRunnables.clear();
        boolean hasValidTrack = false;

        for (int i = 0; i < 16; i++) {
            String musicText = trackEditTexts[i].getText().toString().trim();
            if (TextUtils.isEmpty(musicText)) {
                allTrackEvents.add(new ArrayList<>());
                trackTempos.add(defaultTempo);
                trackKeys.add(defaultKey);
                continue;
            }

            ParsedMusic parsedMusic = parseMusicWithParameters(musicText);
            if (!parsedMusic.noteEvents.isEmpty()) {
                allTrackEvents.add(parsedMusic.noteEvents);
                trackTempos.add(parsedMusic.tempo);
                trackKeys.add(parsedMusic.key);
                hasValidTrack = true;

                selectInstrument(parsedMusic.instrument, i);
            } else {
                allTrackEvents.add(new ArrayList<>());
                trackTempos.add(defaultTempo);
                trackKeys.add(defaultKey);
            }
        }

        if (!hasValidTrack) {
            Toast.makeText(this, "没有有效的音轨内容", Toast.LENGTH_SHORT).show();
            return;
        }

        for (int i = 0; i < 16; i++) {
            currentNoteIndices[i] = 0;
        }

        isPlaying = true;
        shouldLoop = checkBoxLoop.isChecked();

        // 为每个音轨创建独立的播放任务
        for (int i = 0; i < 16; i++) {
            final int track = i;
            if (!allTrackEvents.get(track).isEmpty()) {
                Runnable trackRunnable = createTrackRunnable(track);
                trackRunnables.add(trackRunnable);
                handler.post(trackRunnable);
            }
        }
    }

    private Runnable createTrackRunnable(final int track) {
        return new Runnable() {
            @Override
            public void run() {
                if (!isPlaying) return;

                List<NoteEvent> trackEvents = allTrackEvents.get(track);
                if (trackEvents.isEmpty() || currentNoteIndices[track] >= trackEvents.size()) {
                    if (shouldLoop) {
                        currentNoteIndices[track] = 0;
                    } else {
                        return;
                    }
                }

                NoteEvent currentEvent = trackEvents.get(currentNoteIndices[track]);
                String currentKey = trackKeys.get(track);

                // 播放音符
                if (currentEvent.notes != null && !currentEvent.notes.isEmpty()) {
                    for (String noteStr : currentEvent.notes) {
                        int noteNumber = convertToMidiNote(noteStr, currentKey);
                        if (noteNumber >= 0) {
                            playNote(noteNumber, track);
                        }
                    }
                }

                final int delay = currentEvent.duration;

                // 安排停止和播放下一个音符
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (!isPlaying) return;

                        // 停止当前音符
                        if (currentNoteIndices[track] < trackEvents.size()) {
                            NoteEvent event = trackEvents.get(currentNoteIndices[track]);
                            String eventKey = trackKeys.get(track);
                            if (event.notes != null) {
                                for (String noteStr : event.notes) {
                                    int noteNumber = convertToMidiNote(noteStr, eventKey);
                                    if (noteNumber >= 0) {
                                        stopNote(noteNumber, track);
                                    }
                                }
                            }
                        }

                        currentNoteIndices[track]++;

                        // 检查是否循环或继续播放
                        if (currentNoteIndices[track] >= trackEvents.size()) {
                            if (shouldLoop) {
                                currentNoteIndices[track] = 0;
                            } else {
                                // 检查所有音轨是否都结束
                                boolean allFinished = true;
                                for (int i = 0; i < 16; i++) {
                                    if (!allTrackEvents.get(i).isEmpty() &&
                                            currentNoteIndices[i] < allTrackEvents.get(i).size()) {
                                        allFinished = false;
                                        break;
                                    }
                                }
                                if (allFinished) {
                                    isPlaying = false;
                                    return;
                                }
                                return;
                            }
                        }

                        // 继续播放下一个音符
                        handler.post(createTrackRunnable(track));
                    }
                }, delay);
            }
        };
    }

    private void stopPlaying() {
        isPlaying = false;
        handler.removeCallbacksAndMessages(null);
        trackRunnables.clear();

        for (int track = 0; track < 16; track++) {
            for (int i = 0; i < 128; i++) {
                stopNote(i, track);
            }
        }
    }



    private void playAllTracksIndependently() {
        if (!isPlaying) {
            return;
        }

        // 检查是否所有音轨都已结束
        if (checkAllTracksFinished()) {
            if (shouldLoop) {
                // 循环播放:重置所有音轨索引
                for (int i = 0; i < 16; i++) {
                    currentNoteIndices[i] = 0;
                }
                playAllTracksIndependently();
            } else {
                isPlaying = false;
            }
            return;
        }

        // 为每个音轨安排独立的时间调度
        for (int track = 0; track < 16; track++) {
            final int currentTrack = track;
            List<NoteEvent> trackEvents = allTrackEvents.get(currentTrack);

            // 检查当前音轨是否有事件,以及当前索引是否有效
            if (trackEvents.isEmpty() || currentNoteIndices[currentTrack] >= trackEvents.size()) {
                continue; // 跳过已结束或无效的音轨
            }

            NoteEvent currentEvent = trackEvents.get(currentNoteIndices[currentTrack]);

            // 播放当前音符
            if (currentEvent.notes != null && !currentEvent.notes.isEmpty()) {
                for (String noteStr : currentEvent.notes) {
                    // 在 playAllTracksIndependently() 方法中
                    int noteNumber = convertToMidiNote(noteStr, trackKeys.get(currentTrack));
                    if (noteNumber >= 0) {
                        playNote(noteNumber, currentTrack);
                    }
                }
            }

            // 计算当前音符的持续时间
            int delay = currentEvent.duration;

            // 安排停止音符和播放下一个音符
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (!isPlaying) return;

                    // 再次检查索引有效性
                    if (currentNoteIndices[currentTrack] >= trackEvents.size()) {
                        return;
                    }

                    // 停止当前音符
                    NoteEvent event = trackEvents.get(currentNoteIndices[currentTrack]);
                    if (event.notes != null) {
                        for (String noteStr : event.notes) {
                            int noteNumber = convertToMidiNote(noteStr, trackKeys.get(currentTrack));
                            if (noteNumber >= 0) {
                                stopNote(noteNumber, currentTrack);
                            }
                        }
                    }

                    // 前进到下一个音符
                    currentNoteIndices[currentTrack]++;

                    // 继续播放下一个音符
                    handler.post(() -> playAllTracksIndependently());
                }
            }, delay);
        }
    }

    private boolean checkAllTracksFinished() {
        for (int i = 0; i < 16; i++) {
            List<NoteEvent> trackEvents = allTrackEvents.get(i);
            if (!trackEvents.isEmpty() && currentNoteIndices[i] < trackEvents.size()) {
                return false; // 至少还有一个音轨没播完
            }
        }
        return true; // 所有音轨都结束了
    }

    private ParsedMusic parseMusicWithParameters(String musicText) {
        ParsedMusic result = new ParsedMusic();
        result.instrument = defaultInstrument;
        result.tempo = defaultTempo;
        result.key = defaultKey;

        // 检查是否有参数部分 [instrument,tempo,key]
        Pattern paramPattern = Pattern.compile("^\\[(\\d+),(\\d+),([^\\]]+)\\](.*)");
        Matcher paramMatcher = paramPattern.matcher(musicText);

        String actualMusicText;
        if (paramMatcher.find()) {
            // 提取参数
            try {
                result.instrument = Integer.parseInt(paramMatcher.group(1));
                result.tempo = Integer.parseInt(paramMatcher.group(2));
                result.key = paramMatcher.group(3);
                actualMusicText = paramMatcher.group(4);
            } catch (NumberFormatException e) {
                // 参数格式错误,使用默认值
                actualMusicText = musicText;
            }
        } else {
            // 没有参数部分,使用整个文本作为简谱
            actualMusicText = musicText;
        }

        // 解析简谱
        result.noteEvents = parseMusicNotes(actualMusicText, result.tempo);
        return result;
    }

    private List<NoteEvent> parseMusicNotes(String musicText, int tempo) {
        List<NoteEvent> events = new ArrayList<>();

        if (TextUtils.isEmpty(musicText)) {
            return events;
        }

        // 移除空格和竖线分隔符
        String cleanedText = musicText.replaceAll("\\s+", "").replaceAll("\\|", "");

        // 计算每拍的毫秒数 (60000ms / 每分钟拍数)
        int beatDuration = 60000 / tempo;

        // 匹配模式:音符、休止符、和弦、减半时值
        Pattern pattern = Pattern.compile("(\\d[#']*\\.*)|(0+)|(-+)|(\\[.*?\\])|(\\(.*?\\/)");
        Matcher matcher = pattern.matcher(cleanedText);

        while (matcher.find()) {
            String group = matcher.group();

            if (group.startsWith("[")) {
                // 和弦处理:多个音符一起占一拍
                String chordNotes = group.substring(1, group.length() - 1);
                events.add(new NoteEvent(chordNotes, beatDuration, true));
            } else if (group.startsWith("(")) {
                // 减半时值处理:拍长减半
                String fastNotes = group.substring(1, group.length() - 1);
                events.add(new NoteEvent(fastNotes, beatDuration / 2, false));
            } else if (group.startsWith("-")) {
                // 休止符处理:延长拍
                events.add(new NoteEvent(null, group.length() * beatDuration, false));
            } else if (group.startsWith("0")) {
                // 无声处理
                events.add(new NoteEvent(null, group.length() * beatDuration, false));
            } else {
                // 单个音符处理
                events.add(new NoteEvent(group, beatDuration, false));
            }
        }

        return events;
    }

    private int convertToMidiNote(String noteStr, String key) {
        if (noteStr == null || noteStr.isEmpty()) {
            return -1;
        }

        // 确保只包含有效字符
        if (!noteStr.matches("[0-7#'.]+")) {
            return -1;
        }

        // 提取基础音符数字
        char baseNoteChar = noteStr.charAt(0);
        if (baseNoteChar < '0' || baseNoteChar > '7') {
            return -1;
        }

        int baseNote;
        switch (baseNoteChar) {
            case '1': baseNote = 60; break; // C4
            case '2': baseNote = 62; break; // D4
            case '3': baseNote = 64; break; // E4
            case '4': baseNote = 65; break; // F4
            case '5': baseNote = 67; break; // G4
            case '6': baseNote = 69; break; // A4
            case '7': baseNote = 71; break; // B4
            default: return -1;
        }

        // 处理升调 (#)
        if (noteStr.contains("#")) {
            baseNote++;
        }

        // 处理八度变化 (. 和 ')
        int dotCount = countChars(noteStr, '.');
        int quoteCount = countChars(noteStr, '\'');

        int octaveChange = quoteCount - dotCount;
        baseNote += octaveChange * 12;

        // 应用调式偏移
        Integer keyOffset = KEY_SIGNATURES.get(key);
        if (keyOffset != null) {
            baseNote += keyOffset;
        }

        // 确保音符在有效MIDI范围内 (0-127)
        if (baseNote < 0) {
            Log.w("MidiTest", "音符太低: " + noteStr + " -> MIDI " + baseNote + ", 已调整为21 (A0)");
            return 21; // 调整为A0,这是大多数钢琴的最低音
        } else if (baseNote > 127) {
            Log.w("MidiTest", "音符太高: " + noteStr + " -> MIDI " + baseNote + ", 已调整为108 (C8)");
            return 108; // 调整为C8,这是大多数钢琴的最高音
        }

        return baseNote;
    }

    // 添加一个方法来检查音符是否在可听范围内
    private boolean isNoteAudible(int midiNote) {
        // 只是记录警告,但不阻止播放
        if (midiNote < 21) {
            Log.w("MidiTest", "低音警告: MIDI " + midiNote + " 可能无法播放");
        } else if (midiNote > 108) {
            Log.w("MidiTest", "高音警告: MIDI " + midiNote + " 可能无法播放");
        }
        return true; // 总是尝试播放
    }


    // 如果需要更详细的调试信息,可以添加这个方法
    private void debugNoteConversion(String noteStr, int midiNote) {
        if (midiNote >= 0) {
            String noteName = getNoteName(midiNote);
            Log.d("MidiTest", "转换: " + noteStr + " -> MIDI " + midiNote + " (" + noteName + ")");
        } else {
            Log.d("MidiTest", "无效音符: " + noteStr);
        }
    }
    // 可选:将MIDI音符编号转换为音符名称
    private String getNoteName(int midiNote) {
        String[] noteNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
        int octave = (midiNote / 12) - 1;
        int noteIndex = midiNote % 12;
        return noteNames[noteIndex] + octave + " (" + midiNote + ")";
    }




    private int countChars(String str, char ch) {
        int count = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == ch) {
                count++;
            }
        }
        return count;
    }

    private void playNote(int noteNumber, int track) {
        if (!isNoteAudible(noteNumber)) {
            Log.w("MidiTest", "跳过可能无法播放的音符: MIDI " + noteNumber + " (" + getNoteName(noteNumber) + ")");
            return;
        }

        event = new byte[3];
        event[0] = (byte) (0x90 | track);  // Note On, 使用不同的通道
        event[1] = (byte) noteNumber;
        event[2] = (byte) 0x7F;  // Maximum velocity
        midiDriver.write(event);

        Log.d("MidiTest", "播放音轨" + track + ": MIDI " + noteNumber + " (" + getNoteName(noteNumber) + ")");
    }

    private void stopNote(int noteNumber, int track) {
        event = new byte[3];
        event[0] = (byte) (0x80 | track);  // Note Off, 使用不同的通道
        event[1] = (byte) noteNumber;
        event[2] = (byte) 0x00;  // Minimum velocity
        midiDriver.write(event);
    }

    private void selectInstrument(int instrument, int track) {
        event = new byte[2];
        event[0] = (byte)(0xC0 | track); // Program change, 使用不同的通道
        event[1] = (byte)instrument;
        midiDriver.write(event);
    }


    // 添加导出MIDI文件的方法
    private void exportToMidi() {
        try {
            // 解析所有音轨
            List<ParsedMusic> allParsedMusic = new ArrayList<>();
            boolean hasValidTrack = false;

            for (int i = 0; i < 16; i++) {
                String musicText = trackEditTexts[i].getText().toString().trim();
                if (TextUtils.isEmpty(musicText)) {
                    allParsedMusic.add(null);
                    continue;
                }

                ParsedMusic parsedMusic = parseMusicWithParameters(musicText);
                if (!parsedMusic.noteEvents.isEmpty()) {
                    allParsedMusic.add(parsedMusic);
                    hasValidTrack = true;
                } else {
                    allParsedMusic.add(null);
                }
            }

            if (!hasValidTrack) {
                Toast.makeText(this, "没有有效的音轨内容", Toast.LENGTH_SHORT).show();
                return;
            }

            // 生成MIDI文件(使用格式1和时间缩放)
            byte[] midiData = generateMidiData(allParsedMusic);

            // 保存到文件
            File file = new File(getExternalFilesDir(null), "composition.mid");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(midiData);
                Toast.makeText(this, "MIDI文件已保存: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
                Log.d("MidiTest", "MIDI文件保存成功: " + file.getAbsolutePath());
            }

        } catch (Exception e) {
            Log.e("MidiTest", "导出MIDI文件失败", e);
            Toast.makeText(this, "导出失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    // 生成MIDI文件数据
    private byte[] generateMidiData(List<ParsedMusic> allParsedMusic) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        // 计算有效音轨数量
        int validTrackCount = 0;
        for (ParsedMusic music : allParsedMusic) {
            if (music != null && !music.noteEvents.isEmpty()) {
                validTrackCount++;
            }
        }

        if (validTrackCount == 0) {
            throw new IOException("没有有效的音轨内容");
        }

        // 添加全局音轨
        validTrackCount++;

        // MIDI文件头 - 使用格式1(多音轨)
        writeMidiHeader(baos, 1, validTrackCount, 480);

        // 选择基准速度(使用第一个有效音轨的速度)
        int baseTempo = 120;
        for (ParsedMusic music : allParsedMusic) {
            if (music != null && !music.noteEvents.isEmpty()) {
                baseTempo = music.tempo;
                break;
            }
        }

        // 写入全局音轨(使用基准速度)
        writeGlobalTrack(baos, baseTempo);

        // 为每个有效音轨写入单独的音轨数据(使用时间缩放)
        for (int track = 0; track < 16; track++) {
            ParsedMusic music = allParsedMusic.get(track);
            if (music != null && !music.noteEvents.isEmpty()) {
                writeTrackWithTimeScaling(baos, music, track, baseTempo);
            }
        }

        return baos.toByteArray();
    }


    private void writeGlobalTrack(ByteArrayOutputStream baos, int baseTempo) throws IOException {
        ByteArrayOutputStream trackData = new ByteArrayOutputStream();

        // 时间签名(4/4拍)
        trackData.write(0x00); // delta time
        trackData.write(0xFF); // Meta event
        trackData.write(0x58); // Time signature
        trackData.write(0x04); // Length
        trackData.write(0x04); // 4/4拍
        trackData.write(0x02);
        trackData.write(0x18);
        trackData.write(0x08);

        // 设置基准速度
        int microsecPerQuarterNote = 60000000 / baseTempo;
        trackData.write(0x00); // delta time
        trackData.write(0xFF); // Meta event
        trackData.write(0x51); // Tempo
        trackData.write(0x03); // Length
        trackData.write((microsecPerQuarterNote >> 16) & 0xFF);
        trackData.write((microsecPerQuarterNote >> 8) & 0xFF);
        trackData.write(microsecPerQuarterNote & 0xFF);

        // 音轨结束
        trackData.write(0x00);
        trackData.write(0xFF);
        trackData.write(0x2F);
        trackData.write(0x00);

        // 写入全局音轨
        baos.write("MTrk".getBytes());
        byte[] trackBytes = trackData.toByteArray();
        baos.write(intToBytes(trackBytes.length, 4));
        baos.write(trackBytes);
    }

    // MIDI事件辅助类
    private static class MidiEvent {
        long deltaTime;
        byte[] eventData;
    }

    private void writeTrack(ByteArrayOutputStream baos, ParsedMusic music, int track, int globalTempo) throws IOException {
        ByteArrayOutputStream trackData = new ByteArrayOutputStream();

        // 设置音色
        trackData.write(0x00);
        trackData.write((byte) (0xC0 | (track & 0x0F)));
        trackData.write((byte) (music.instrument & 0x7F));

        // 使用全局速度来计算时间
        int ticksPerQuarterNote = 480;
        int microsecondsPerQuarterNote = 60000000 / globalTempo;

        int cumulativeDelta = 0;

        for (NoteEvent event : music.noteEvents) {
            // 使用全局速度计算ticks
            int eventTicks = (int) ((event.duration * ticksPerQuarterNote * 1000.0) / microsecondsPerQuarterNote);
            if (eventTicks <= 0) eventTicks = 1;

            if (event.notes != null && !event.notes.isEmpty()) {
                // Note On 事件
                for (int i = 0; i < event.notes.size(); i++) {
                    String noteStr = event.notes.get(i);
                    int noteNumber = convertToMidiNote(noteStr, music.key);
                    if (noteNumber >= 0 && noteNumber <= 127) {
                        if (i == 0) {
                            writeVarLen(trackData, cumulativeDelta);
                            cumulativeDelta = 0;
                        } else {
                            writeVarLen(trackData, 0);
                        }

                        trackData.write((byte) (0x90 | (track & 0x0F)));
                        trackData.write((byte) noteNumber);
                        trackData.write((byte) 0x7F);
                    }
                }

                cumulativeDelta += eventTicks;
            } else {
                cumulativeDelta += eventTicks;
            }
        }

        // 音轨结束
        writeVarLen(trackData, cumulativeDelta);
        trackData.write(0xFF);
        trackData.write(0x2F);
        trackData.write(0x00);

        // 写入音轨
        baos.write("MTrk".getBytes());
        byte[] trackBytes = trackData.toByteArray();
        baos.write(intToBytes(trackBytes.length, 4));
        baos.write(trackBytes);
    }
    private void writeTrackWithTimeScaling(ByteArrayOutputStream baos, ParsedMusic music, int track, int baseTempo) throws IOException {
        ByteArrayOutputStream trackData = new ByteArrayOutputStream();

        // 设置音色
        trackData.write(0x00); // delta time
        trackData.write((byte) (0xC0 | (track & 0x0F))); // Program change
        trackData.write((byte) (music.instrument & 0x7F));

        // 计算速度比例因子
        double tempoRatio = (double) baseTempo / music.tempo;
        int ticksPerQuarterNote = 480;
        int baseMicrosecPerQuarterNote = 60000000 / baseTempo;

        long cumulativeDelta = 0;
        List<Integer> activeNotes = new ArrayList<>();

        for (NoteEvent event : music.noteEvents) {
            // 计算原始ticks和缩放后的ticks
            int originalTicks = (int) ((event.duration * ticksPerQuarterNote * 1000.0) / baseMicrosecPerQuarterNote);
            int scaledTicks = (int) (originalTicks * tempoRatio);
            if (scaledTicks <= 0) scaledTicks = 1;

            if (event.notes != null && !event.notes.isEmpty()) {
                // 先关闭所有当前活动的音符
                if (!activeNotes.isEmpty()) {
                    writeVarLen(trackData, (int) cumulativeDelta);
                    cumulativeDelta = 0;
                    for (int note : activeNotes) {
                        trackData.write((byte) (0x80 | (track & 0x0F))); // Note Off
                        trackData.write((byte) note);
                        trackData.write((byte) 0x00); // velocity
                    }
                    activeNotes.clear();
                }

                // 打开新音符(使用正确的调式)
                writeVarLen(trackData, (int) cumulativeDelta);
                cumulativeDelta = 0;

                for (String noteStr : event.notes) {
                    int noteNumber = convertToMidiNote(noteStr, music.key);
                    if (noteNumber >= 0 && noteNumber <= 127) {
                        trackData.write((byte) (0x90 | (track & 0x0F))); // Note On
                        trackData.write((byte) noteNumber);
                        trackData.write((byte) 0x7F); // velocity
                        activeNotes.add(noteNumber);
                    }
                }

                cumulativeDelta = scaledTicks;
            } else {
                // 休止符
                cumulativeDelta += scaledTicks;
            }
        }

        // 关闭所有剩余的音符
        if (!activeNotes.isEmpty()) {
            writeVarLen(trackData, (int) cumulativeDelta);
            for (int note : activeNotes) {
                trackData.write((byte) (0x80 | (track & 0x0F))); // Note Off
                trackData.write((byte) note);
                trackData.write((byte) 0x00); // velocity
            }
        }

        // 音轨结束
        trackData.write(0x00);
        trackData.write(0xFF);
        trackData.write(0x2F);
        trackData.write(0x00);

        // 写入音轨
        baos.write("MTrk".getBytes());
        byte[] trackBytes = trackData.toByteArray();
        baos.write(intToBytes(trackBytes.length, 4));
        baos.write(trackBytes);
    }


    // 写入MIDI文件头
    private void writeMidiHeader(ByteArrayOutputStream baos, int format, int numTracks, int division) throws IOException {
        // "MThd"
        baos.write('M');
        baos.write('T');
        baos.write('h');
        baos.write('d');

        // 头部长度 (6 bytes)
        baos.write(0x00);
        baos.write(0x00);
        baos.write(0x00);
        baos.write(0x06);

        // 格式类型 (0, 1, or 2)
        baos.write((byte) ((format >> 8) & 0xFF));
        baos.write((byte) (format & 0xFF));

        // 音轨数
        baos.write((byte) ((numTracks >> 8) & 0xFF));
        baos.write((byte) (numTracks & 0xFF));

        // 时间分割 (ticks per quarter note)
        baos.write((byte) ((division >> 8) & 0xFF));
        baos.write((byte) (division & 0xFF));
    }

    // 写入可变长度值
    private void writeVarLen(ByteArrayOutputStream baos, int value) throws IOException {
        if (value < 0) {
            value = 0;
        }

        int buffer = value & 0x7F;
        while ((value >>= 7) > 0) {
            buffer <<= 8;
            buffer |= 0x80;
            buffer += (value & 0x7F);
        }

        while (true) {
            baos.write((byte) (buffer & 0xFF));
            if ((buffer & 0x80) != 0) {
                buffer >>= 8;
            } else {
                break;
            }
        }
    }

    // 整数转字节数组
    private byte[] intToBytes(int value, int length) {
        ByteBuffer buffer = ByteBuffer.allocate(length);
        buffer.order(ByteOrder.BIG_ENDIAN);

        if (length == 2) {
            buffer.putShort((short) value);
        } else if (length == 4) {
            buffer.putInt(value);
        }

        return buffer.array();
    }







    // 解析后的音乐数据类
    private static class ParsedMusic {
        int instrument;
        int tempo;
        String key;
        List<NoteEvent> noteEvents = new ArrayList<>();
    }

    // 音符事件类
    private static class NoteEvent {
        List<String> notes;
        int duration;
        boolean isChord;

        NoteEvent(String noteData, int baseDuration, boolean chord) {
            this.isChord = chord;
            this.duration = baseDuration;

            if (noteData != null) {
                notes = new ArrayList<>();
                if (chord) {
                    // 和弦:将多个音符分开
                    for (char c : noteData.toCharArray()) {
                        if (c >= '1' && c <= '7') {
                            notes.add(String.valueOf(c));
                        }
                    }
                } else {
                    // 单个音符
                    notes.add(noteData);
                }
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="@android:color/darker_gray"
    tools:context="com.example.miditest.MainActivity">

    <!-- 上半部分:音轨文本框容器(所有文本框重叠在同一位置) -->
    <FrameLayout
        android:id="@+id/trackContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginBottom="8dp">

        <EditText
            android:id="@+id/editTextTrack1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="音轨1: [音色,速度,调式]简谱内容"
            android:inputType="textMultiLine"
            android:minLines="3"
            android:scrollbars="vertical"
            android:visibility="visible"
            android:text="[0,220,G大调]5-351'--76-1'-5---5-123-212--5-351'--76-1'-5---5-234--7.1--"/>

        <!-- 其他15个音轨文本框将动态添加 -->

    </FrameLayout>

    <!-- 分隔线 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/white"
        android:layout_marginVertical="4dp"/>

    <!-- 下半部分:控制按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="4">

        <Button
            android:id="@+id/buttonPlay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="播放"
            android:layout_margin="2dp"/>

        <Button
            android:id="@+id/buttonStop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止"
            android:layout_margin="2dp"/>

        <Button
            android:id="@+id/buttonExport"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="导出MIDI"
            android:layout_margin="2dp"/>

        <CheckBox
            android:id="@+id/checkBoxLoop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="循环播放"
            android:layout_margin="2dp"/>
    </LinearLayout>

    <!-- 音轨选择按钮网格 -->
    <GridView
        android:id="@+id/trackButtonsGrid"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:numColumns="4"
        android:verticalSpacing="4dp"
        android:horizontalSpacing="4dp"
        android:stretchMode="columnWidth"/>

</LinearLayout>

midi16JPBFQ\app\libs\MidiDriver-1.21.aar

通过网盘分享的文件:MidiDriver-1.21.aar
链接: https://pan.baidu.com/s/1a3wwy0H4OrKJdkTTKAsL9A 提取码: itkg

简谱播放器apk下载地址:

app-debug-midi16JPBFQ-ZXQMQZQ.apk

https://download.csdn.net/download/qq_32257509/91870337

简谱播放器项目源代码下载地址:
midi16JPBFQ-ZXQMQZQ.zip
https://download.csdn.net/download/qq_32257509/91870336

通过网盘分享的文件:app-debug-midi16JPBFQ-ZXQMQZQ.apk
链接: https://pan.baidu.com/s/14n0Yy3ebXceDBUijlFhw3g?pwd=yet8 提取码: yet8

通过网盘分享的文件:midi16JPBFQ-ZXQMQZQ.zip
链接: https://pan.baidu.com/s/1lUdS–ibTFUVgTzIFcFGzw?pwd=dumn 提取码: dumn

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Esoftyr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值