From ab995b0be47697ee9c85dcb494feea1c447a9e82 Mon Sep 17 00:00:00 2001 From: swadical Date: Sat, 2 Jan 2021 15:29:21 +1030 Subject: [PATCH] Upgrade audio logic + fix subdivisions --- lib/controllers/muonproject.dart | 1 + lib/controllers/muonvoice.dart | 7 +- lib/editor.dart | 121 ++++++++++++++++++++++++++----- lib/pianoroll.dart | 40 +++++----- pubspec.lock | 6 +- pubspec.yaml | 3 +- 6 files changed, 132 insertions(+), 46 deletions(-) diff --git a/lib/controllers/muonproject.dart b/lib/controllers/muonproject.dart index d6dfd01..ac2b981 100644 --- a/lib/controllers/muonproject.dart +++ b/lib/controllers/muonproject.dart @@ -24,6 +24,7 @@ class MuonProjectController extends GetxController { final selectedNotes = Map().obs; final playheadTime = 0.0.obs; List copiedNotes = []; + final internalStatus = "idle".obs; // subdivision manager final currentSubdivision = 1.obs; diff --git a/lib/controllers/muonvoice.dart b/lib/controllers/muonvoice.dart index 88898cd..58a7ce8 100644 --- a/lib/controllers/muonvoice.dart +++ b/lib/controllers/muonvoice.dart @@ -144,15 +144,18 @@ class MuonVoiceController extends GetxController { } AudioPlayer audioPlayer; - Future getAudioPlayer() async { + int audioPlayerDuration = 0; + Future getAudioPlayer([Duration playPos]) async { final voiceID = project.voices.indexOf(this); if(audioPlayer == null) { audioPlayer = new AudioPlayer(id: voiceID); } + await audioPlayer.unload(); final suc = await audioPlayer.load(project.getProjectFilePath("audio/" + voiceID.toString() + "_voice_world.wav")); - audioPlayer.setPosition(Duration(seconds: 2)); + audioPlayerDuration = (await audioPlayer.getDuration()).inMilliseconds; + audioPlayer.setPosition(playPos ?? Duration(seconds: 2)); if(!suc) { audioPlayer = null; diff --git a/lib/editor.dart b/lib/editor.dart index d5ede03..caeb3e8 100644 --- a/lib/editor.dart +++ b/lib/editor.dart @@ -28,6 +28,62 @@ class MuonEditor extends StatefulWidget { class _MuonEditorState extends State { bool _firstTimeSetupDone = false; + Future _playAudio() async { + if(currentProject.internalStatus.value != "idle") {return;} + + final playPos = Duration( + milliseconds: 2000 + + ( + 1000 * + ( + currentProject.playheadTime.value / + (currentProject.bpm.value / 60) + ) + ).floor() + ); + for(final voice in currentProject.voices) { + if(voice.audioPlayer != null) { + await voice.audioPlayer.unload(); + } + + currentProject.internalStatus.value = "compiling"; + await voice.makeLabels(); + await voice.runNeutrino(); + await voice.vocodeWORLD(); + + final audioPlayer = await voice.getAudioPlayer(playPos); + + await audioPlayer.setPosition(playPos); + final suc = await audioPlayer.play(); + + if(suc) { + currentProject.internalStatus.value = "playing"; + } + else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Theme.of(context).errorColor, + content: new Text('Unable to play audio!'), + duration: new Duration(seconds: 5), + ) + ); + } + } + } + + Future _stopAudio() async { + for(final voice in currentProject.voices) { + if(voice.audioPlayer != null) { + voice.audioPlayer.unload(); + if(currentProject.internalStatus.value == "playing") { + currentProject.internalStatus.value = "idle"; + } + else { + currentProject.playheadTime.value = 0; + } + } + } + } + @override Widget build(BuildContext context) { final settings = getMuonSettings(); @@ -37,9 +93,17 @@ class _MuonEditorState extends State { if(voice != null) { if(voice.audioPlayer != null) { - if(voice.audioPlayer.isPlaying) { - int voicePos = (await voice.audioPlayer.getPosition()).inMilliseconds - 2000; - currentProject.playheadTime.value = voicePos / 1000 * (currentProject.bpm / 60); + if(currentProject.internalStatus.value == "playing") { + int curPos = (await voice.audioPlayer.getPosition()).inMilliseconds; + + if(curPos >= voice.audioPlayerDuration) { + currentProject.playheadTime.value = 0; + currentProject.internalStatus.value = "idle"; + } + else { + int voicePos = curPos - 2000; + currentProject.playheadTime.value = voicePos / 1000 * (currentProject.bpm / 60); + } } } } @@ -137,28 +201,37 @@ class _MuonEditorState extends State { title: Text("Muon Editor"), actions: [ IconButton( + icon: const Icon(Icons.exposure_plus_1), + tooltip: "Add subdivision", + onPressed: () { + currentProject.setSubdivision(currentProject.currentSubdivision.value + 1); + }, + ), + IconButton( + icon: const Icon(Icons.exposure_minus_1), + tooltip: "Subtract subdivision", + onPressed: () { + currentProject.setSubdivision(max(1,currentProject.currentSubdivision.value - 1)); + }, + ), + SizedBox(width: 40,), + Obx(() => IconButton( icon: const Icon(Icons.play_arrow), tooltip: "Play", - onPressed: () async { - for(final voice in currentProject.voices) { - await voice.makeLabels(); - await voice.runNeutrino(); - await voice.vocodeWORLD(); - - final audioPlayer = await voice.getAudioPlayer(); - - final suc = await audioPlayer.play(); - - if(suc) { - } - } + color: currentProject.internalStatus.value == "compiling" ? + Colors.yellow : + currentProject.internalStatus.value == "playing" ? + Colors.green : + Colors.white, + onPressed: () { + _playAudio(); }, - ), + )), IconButton( icon: const Icon(Icons.stop), tooltip: "Stop", onPressed: () { - + _stopAudio(); }, ), SizedBox(width: 40,), @@ -379,8 +452,8 @@ class _MuonEditorState extends State { final deltaSemiTones = (pianoRoll.painter.screenPixelsToSemitones(mousePos.y - mouseFirstPos.y) + fpDeltaSemiTones).floor(); final fpDeltaBeats = (pianoRoll.painter.getBeatNumAtCursor(mouseFirstPos.x) % 1); - final deltaBeats = pianoRoll.painter.screenPixelsToBeats(mousePos.x - mouseFirstPos.x) + fpDeltaBeats / currentProject.currentSubdivision.value; - final deltaSegments = deltaBeats * currentProject.currentSubdivision.value; + final deltaBeats = pianoRoll.painter.screenPixelsToBeats(mousePos.x - mouseFirstPos.x) + fpDeltaBeats / currentProject.timeUnitsPerBeat.value; + final deltaSegments = deltaBeats * currentProject.timeUnitsPerBeat.value; final deltaSegmentsFixed = deltaSegments.floor(); for(final selectedNote in currentProject.selectedNotes.keys) { @@ -537,6 +610,14 @@ class _MuonEditorState extends State { // dumb hack to force repaint pianoRoll.state.setState(() {}); } + else if(keyEvent.isKeyPressed(LogicalKeyboardKey.space)) { + if(currentProject.internalStatus.value == "playing") { + _stopAudio(); + } + else { + _playAudio(); + } + } }, )) ), diff --git a/lib/pianoroll.dart b/lib/pianoroll.dart index 1bf194c..c72299c 100644 --- a/lib/pianoroll.dart +++ b/lib/pianoroll.dart @@ -514,9 +514,9 @@ class PianoRollPainter extends CustomPainter { for (final voice in project.voices) { for (final note in voice.notes) { - var noteX = note.startAtTime * pixelsPerBeat / project.currentSubdivision.value; + var noteX = note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value; var noteY = pitchToYAxis(note); - var noteW = note.duration * pixelsPerBeat / project.currentSubdivision.value; + var noteW = note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value; var noteH = 20; if ((noteX < canvasPos.x) && @@ -532,9 +532,9 @@ class PianoRollPainter extends CustomPainter { } Rect getNoteRect(MuonNoteController note) { - var noteL = note.startAtTime * pixelsPerBeat / project.currentSubdivision.value; + var noteL = note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value; var noteT = pitchToYAxis(note); - var noteR = noteL + (note.duration * pixelsPerBeat / project.currentSubdivision.value); + var noteR = noteL + (note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value); var noteB = noteT + 20; return Rect.fromLTRB(noteL, noteT, noteR, noteB); @@ -673,33 +673,33 @@ class PianoRollPainter extends CustomPainter { if(themeData.brightness == Brightness.dark) { canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value, pitchToYAxis(note), - note.duration * pixelsPerBeat / project.currentSubdivision.value, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value, 20), Paint()..color = Colors.white); canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value, pitchToYAxis(note), - note.duration * pixelsPerBeat / project.currentSubdivision.value, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value, 20), Paint()..color = noteColor.withOpacity(0.75)); } else { canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value, pitchToYAxis(note), - note.duration * pixelsPerBeat / project.currentSubdivision.value, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value, 20), Paint()..color = noteColor.withOpacity(0.5)); } canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value + xBorderThickness / xScale, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value + xBorderThickness / xScale, pitchToYAxis(note) + yBorderThickness / yScale, - note.duration * pixelsPerBeat / project.currentSubdivision.value - xBorderThickness / xScale * 2, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value - xBorderThickness / xScale * 2, 20 - yBorderThickness / yScale * 2), Paint()..color = noteColor); } @@ -707,9 +707,9 @@ class PianoRollPainter extends CustomPainter { if(themeData.brightness == Brightness.light) { canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value, pitchToYAxis(note), - note.duration * pixelsPerBeat / project.currentSubdivision.value, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value, 20), Paint()..color = noteColor); } @@ -718,16 +718,16 @@ class PianoRollPainter extends CustomPainter { final yBorderThickness = 0; canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value, pitchToYAxis(note), - note.duration * pixelsPerBeat / project.currentSubdivision.value + xBorderThickness / xScale, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value + xBorderThickness / xScale, 20), Paint()..color = Colors.black); canvas.drawRect( Rect.fromLTWH( - note.startAtTime * pixelsPerBeat / project.currentSubdivision.value + xBorderThickness / xScale, + note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value + xBorderThickness / xScale, pitchToYAxis(note) + yBorderThickness / yScale, - note.duration * pixelsPerBeat / project.currentSubdivision.value - xBorderThickness / xScale, + note.duration * pixelsPerBeat / project.timeUnitsPerBeat.value - xBorderThickness / xScale, 20 - (yBorderThickness / yScale * 2)), Paint()..color = noteColor.withOpacity(0.95)); } @@ -752,7 +752,7 @@ class PianoRollPainter extends CustomPainter { tp.paint( canvas, new Offset( - (note.startAtTime * pixelsPerBeat / project.currentSubdivision.value + + (note.startAtTime * pixelsPerBeat / project.timeUnitsPerBeat.value + xOffset + pianoKeysWidth / xScale + 20) * @@ -850,7 +850,7 @@ class PianoRollPainter extends CustomPainter { // draw "what am i looking at?" (waila) if(curMousePos != null) { final mouseBeatNum = max(0,getBeatNumAtCursor(curMousePos.x)); - final mouseBeatSubDivNum = (mouseBeatNum * project.currentSubdivision.value).floor() % project.currentSubdivision.value + 1; + final mouseBeatSubDivNum = (mouseBeatNum * project.timeUnitsPerBeat.value).floor() % project.timeUnitsPerBeat.value + 1; final mouseMeasureNum = (mouseBeatNum / project.beatsPerMeasure.value).ceil(); final mousePitch = getPitchAtCursor(curMousePos.y); var wailaLabelPainter = new TextPainter( diff --git a/pubspec.lock b/pubspec.lock index 7924afc..0425d26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -267,9 +267,9 @@ packages: flutter_audio_desktop: dependency: "direct main" description: - name: flutter_audio_desktop - url: "https://pub.dartlang.org" - source: hosted + path: "../flutter_audio_desktop" + relative: true + source: path version: "0.0.8" flutter_driver: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 37c9c81..38e9223 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,7 +50,8 @@ dependencies: get: ^3.24.0 kana_kit: ^1.0.4 path: ^1.7.0 - flutter_audio_desktop: ^0.0.8 + flutter_audio_desktop: + path: ../flutter_audio_desktop dev_dependencies: flutter_test: