initial commit
45
flutter/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
30
flutter/.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "19074d12f7eaf6a8180cd4036a430c1d76de904e"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||
- platform: macos
|
||||
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
38
flutter/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Supertonic Flutter Example
|
||||
|
||||
This example demonstrates how to use Supertonic 2 in a Flutter application using ONNX Runtime.
|
||||
|
||||
> **Note:** This project uses the `flutter_onnxruntime` package ([https://pub.dev/packages/flutter_onnxruntime](https://pub.dev/packages/flutter_onnxruntime)). At the moment, only the macOS platform has been tested. Although the flutter_onnxruntime package supports several other platforms, they have not been tested in this project yet and may require additional verification.
|
||||
|
||||
|
||||
## 📰 Update News
|
||||
|
||||
**2026.01.06** - 🎉 **Supertonic 2** released with multilingual support! Now supports English (`en`), Korean (`ko`), Spanish (`es`), Portuguese (`pt`), and French (`fr`). [Demo](https://huggingface.co/spaces/Supertone/supertonic-2) | [Models](https://huggingface.co/Supertone/supertonic-2)
|
||||
|
||||
**2025.12.10** - Added [6 new voice styles](https://huggingface.co/Supertone/supertonic/tree/b10dbaf18b316159be75b34d24f740008fddd381) (M3, M4, M5, F3, F4, F5). See [Voices](https://supertone-inc.github.io/supertonic-py/voices/) for details
|
||||
|
||||
**2025.12.08** - Optimized ONNX models via [OnnxSlim](https://github.com/inisis/OnnxSlim) now available on [Hugging Face Models](https://huggingface.co/Supertone/supertonic)
|
||||
|
||||
**2025.11.23** - Added and tested macos support.
|
||||
|
||||
## Multilingual Support
|
||||
|
||||
Supertonic 2 supports multiple languages. Select the appropriate language from the dropdown:
|
||||
- **English (en)**: Default language
|
||||
- **한국어 (ko)**: Korean
|
||||
- **Español (es)**: Spanish
|
||||
- **Português (pt)**: Portuguese
|
||||
- **Français (fr)**: French
|
||||
|
||||
## Requirements
|
||||
|
||||
- Flutter SDK version ^3.5.0
|
||||
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
flutter clean
|
||||
flutter pub get
|
||||
flutter run -d macos
|
||||
```
|
||||
|
||||
28
flutter/analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
695
flutter/lib/helper.dart
Normal file
@@ -0,0 +1,695 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:flutter_onnxruntime/flutter_onnxruntime.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
final logger = Logger(
|
||||
printer: PrettyPrinter(methodCount: 0, errorMethodCount: 5, lineLength: 80),
|
||||
);
|
||||
|
||||
// Available languages for multilingual TTS
|
||||
const List<String> availableLangs = ['en', 'ko', 'es', 'pt', 'fr'];
|
||||
|
||||
bool isValidLang(String lang) => availableLangs.contains(lang);
|
||||
|
||||
// Hangul Jamo constants for NFKD decomposition
|
||||
const int _hangulSyllableBase = 0xAC00;
|
||||
const int _hangulSyllableEnd = 0xD7A3;
|
||||
const int _leadingJamoBase = 0x1100;
|
||||
const int _vowelJamoBase = 0x1161;
|
||||
const int _trailingJamoBase = 0x11A7;
|
||||
const int _vowelCount = 21;
|
||||
const int _trailingCount = 28;
|
||||
|
||||
/// Decompose a Hangul syllable into Jamo (NFKD-like decomposition)
|
||||
List<int> _decomposeHangulSyllable(int codePoint) {
|
||||
if (codePoint < _hangulSyllableBase || codePoint > _hangulSyllableEnd) {
|
||||
return [codePoint];
|
||||
}
|
||||
|
||||
final syllableIndex = codePoint - _hangulSyllableBase;
|
||||
final leadingIndex = syllableIndex ~/ (_vowelCount * _trailingCount);
|
||||
final vowelIndex =
|
||||
(syllableIndex % (_vowelCount * _trailingCount)) ~/ _trailingCount;
|
||||
final trailingIndex = syllableIndex % _trailingCount;
|
||||
|
||||
final result = <int>[
|
||||
_leadingJamoBase + leadingIndex,
|
||||
_vowelJamoBase + vowelIndex,
|
||||
];
|
||||
|
||||
if (trailingIndex > 0) {
|
||||
result.add(_trailingJamoBase + trailingIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Common Latin character decompositions (NFKD) for es, pt, fr
|
||||
const Map<int, List<int>> _latinDecompositions = {
|
||||
// Uppercase with acute accent
|
||||
0x00C1: [0x0041, 0x0301], // Á → A + ́
|
||||
0x00C9: [0x0045, 0x0301], // É → E + ́
|
||||
0x00CD: [0x0049, 0x0301], // Í → I + ́
|
||||
0x00D3: [0x004F, 0x0301], // Ó → O + ́
|
||||
0x00DA: [0x0055, 0x0301], // Ú → U + ́
|
||||
// Lowercase with acute accent
|
||||
0x00E1: [0x0061, 0x0301], // á → a + ́
|
||||
0x00E9: [0x0065, 0x0301], // é → e + ́
|
||||
0x00ED: [0x0069, 0x0301], // í → i + ́
|
||||
0x00F3: [0x006F, 0x0301], // ó → o + ́
|
||||
0x00FA: [0x0075, 0x0301], // ú → u + ́
|
||||
// Grave accent
|
||||
0x00C0: [0x0041, 0x0300], // À → A + ̀
|
||||
0x00C8: [0x0045, 0x0300], // È → E + ̀
|
||||
0x00CC: [0x0049, 0x0300], // Ì → I + ̀
|
||||
0x00D2: [0x004F, 0x0300], // Ò → O + ̀
|
||||
0x00D9: [0x0055, 0x0300], // Ù → U + ̀
|
||||
0x00E0: [0x0061, 0x0300], // à → a + ̀
|
||||
0x00E8: [0x0065, 0x0300], // è → e + ̀
|
||||
0x00EC: [0x0069, 0x0300], // ì → i + ̀
|
||||
0x00F2: [0x006F, 0x0300], // ò → o + ̀
|
||||
0x00F9: [0x0075, 0x0300], // ù → u + ̀
|
||||
// Circumflex
|
||||
0x00C2: [0x0041, 0x0302], // Â → A + ̂
|
||||
0x00CA: [0x0045, 0x0302], // Ê → E + ̂
|
||||
0x00CE: [0x0049, 0x0302], // Î → I + ̂
|
||||
0x00D4: [0x004F, 0x0302], // Ô → O + ̂
|
||||
0x00DB: [0x0055, 0x0302], // Û → U + ̂
|
||||
0x00E2: [0x0061, 0x0302], // â → a + ̂
|
||||
0x00EA: [0x0065, 0x0302], // ê → e + ̂
|
||||
0x00EE: [0x0069, 0x0302], // î → i + ̂
|
||||
0x00F4: [0x006F, 0x0302], // ô → o + ̂
|
||||
0x00FB: [0x0075, 0x0302], // û → u + ̂
|
||||
// Tilde
|
||||
0x00C3: [0x0041, 0x0303], // Ã → A + ̃
|
||||
0x00D1: [0x004E, 0x0303], // Ñ → N + ̃
|
||||
0x00D5: [0x004F, 0x0303], // Õ → O + ̃
|
||||
0x00E3: [0x0061, 0x0303], // ã → a + ̃
|
||||
0x00F1: [0x006E, 0x0303], // ñ → n + ̃
|
||||
0x00F5: [0x006F, 0x0303], // õ → o + ̃
|
||||
// Diaeresis/Umlaut
|
||||
0x00C4: [0x0041, 0x0308], // Ä → A + ̈
|
||||
0x00CB: [0x0045, 0x0308], // Ë → E + ̈
|
||||
0x00CF: [0x0049, 0x0308], // Ï → I + ̈
|
||||
0x00D6: [0x004F, 0x0308], // Ö → O + ̈
|
||||
0x00DC: [0x0055, 0x0308], // Ü → U + ̈
|
||||
0x00E4: [0x0061, 0x0308], // ä → a + ̈
|
||||
0x00EB: [0x0065, 0x0308], // ë → e + ̈
|
||||
0x00EF: [0x0069, 0x0308], // ï → i + ̈
|
||||
0x00F6: [0x006F, 0x0308], // ö → o + ̈
|
||||
0x00FC: [0x0075, 0x0308], // ü → u + ̈
|
||||
// Cedilla
|
||||
0x00C7: [0x0043, 0x0327], // Ç → C + ̧
|
||||
0x00E7: [0x0063, 0x0327], // ç → c + ̧
|
||||
};
|
||||
|
||||
/// Apply NFKD-like decomposition (Hangul + Latin accented characters)
|
||||
String _applyNfkdDecomposition(String text) {
|
||||
final result = <int>[];
|
||||
for (final codePoint in text.runes) {
|
||||
// Check Hangul first
|
||||
if (codePoint >= _hangulSyllableBase && codePoint <= _hangulSyllableEnd) {
|
||||
result.addAll(_decomposeHangulSyllable(codePoint));
|
||||
}
|
||||
// Check Latin decomposition
|
||||
else if (_latinDecompositions.containsKey(codePoint)) {
|
||||
result.addAll(_latinDecompositions[codePoint]!);
|
||||
}
|
||||
// Keep as-is
|
||||
else {
|
||||
result.add(codePoint);
|
||||
}
|
||||
}
|
||||
return String.fromCharCodes(result);
|
||||
}
|
||||
|
||||
String preprocessText(String text, String lang) {
|
||||
// Apply NFKD-like decomposition (especially for Hangul syllables → Jamo)
|
||||
text = _applyNfkdDecomposition(text);
|
||||
|
||||
// Remove emojis
|
||||
text = text.replaceAll(
|
||||
RegExp(
|
||||
r'[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|'
|
||||
r'[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|'
|
||||
r'[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]|'
|
||||
r'[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F1E6}-\u{1F1FF}]',
|
||||
unicode: true,
|
||||
),
|
||||
'');
|
||||
|
||||
// Replace various dashes and symbols
|
||||
const replacements = {
|
||||
'–': '-',
|
||||
'‑': '-',
|
||||
'—': '-',
|
||||
'_': ' ',
|
||||
'\u201C': '"',
|
||||
'\u201D': '"',
|
||||
'\u2018': "'",
|
||||
'\u2019': "'",
|
||||
'´': "'",
|
||||
'`': "'",
|
||||
'[': ' ',
|
||||
']': ' ',
|
||||
'|': ' ',
|
||||
'/': ' ',
|
||||
'#': ' ',
|
||||
'→': ' ',
|
||||
'←': ' ',
|
||||
};
|
||||
for (final entry in replacements.entries) {
|
||||
text = text.replaceAll(entry.key, entry.value);
|
||||
}
|
||||
|
||||
// Remove special symbols
|
||||
text = text.replaceAll(RegExp(r'[♥☆♡©\\]'), '');
|
||||
|
||||
// Replace known expressions
|
||||
text = text.replaceAll('@', ' at ');
|
||||
text = text.replaceAll('e.g.,', 'for example, ');
|
||||
text = text.replaceAll('i.e.,', 'that is, ');
|
||||
|
||||
// Fix spacing around punctuation
|
||||
text = text.replaceAll(' ,', ',');
|
||||
text = text.replaceAll(' .', '.');
|
||||
text = text.replaceAll(' !', '!');
|
||||
text = text.replaceAll(' ?', '?');
|
||||
text = text.replaceAll(' ;', ';');
|
||||
text = text.replaceAll(' :', ':');
|
||||
text = text.replaceAll(" '", "'");
|
||||
|
||||
// Remove duplicate quotes
|
||||
while (text.contains('""')) text = text.replaceAll('""', '"');
|
||||
while (text.contains("''")) text = text.replaceAll("''", "'");
|
||||
while (text.contains('``')) text = text.replaceAll('``', '`');
|
||||
|
||||
// Remove extra spaces
|
||||
text = text.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
|
||||
// Add period if needed
|
||||
if (text.isNotEmpty &&
|
||||
!RegExp(r'[.!?;:,\x27\x22\u2018\u2019)\]}…。」』】〉》›»]$').hasMatch(text)) {
|
||||
text += '.';
|
||||
}
|
||||
|
||||
// Validate language
|
||||
if (!isValidLang(lang)) {
|
||||
throw ArgumentError(
|
||||
'Invalid language: $lang. Available: ${availableLangs.join(", ")}');
|
||||
}
|
||||
|
||||
// Wrap text with language tags
|
||||
text = '<$lang>$text</$lang>';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
class UnicodeProcessor {
|
||||
final Map<int, int> indexer;
|
||||
|
||||
UnicodeProcessor._(this.indexer);
|
||||
|
||||
static Future<UnicodeProcessor> load(String path) async {
|
||||
final json = jsonDecode(
|
||||
path.startsWith('assets/')
|
||||
? await rootBundle.loadString(path)
|
||||
: File(path).readAsStringSync(),
|
||||
);
|
||||
|
||||
final indexer = json is List
|
||||
? {
|
||||
for (var i = 0; i < json.length; i++)
|
||||
if (json[i] is int && json[i] >= 0) i: json[i] as int
|
||||
}
|
||||
: (json as Map<String, dynamic>)
|
||||
.map((k, v) => MapEntry(int.parse(k), v as int));
|
||||
|
||||
return UnicodeProcessor._(indexer);
|
||||
}
|
||||
|
||||
Map<String, dynamic> call(List<String> textList, List<String> langList) {
|
||||
// Preprocess texts with language tags
|
||||
final processedTexts = <String>[];
|
||||
for (var i = 0; i < textList.length; i++) {
|
||||
processedTexts.add(preprocessText(textList[i], langList[i]));
|
||||
}
|
||||
|
||||
final lengths = processedTexts.map((t) => t.runes.length).toList();
|
||||
final maxLen = lengths.reduce(math.max);
|
||||
|
||||
final textIds = processedTexts.map((text) {
|
||||
final row = List<int>.filled(maxLen, 0);
|
||||
final runes = text.runes.toList();
|
||||
for (var i = 0; i < runes.length; i++) {
|
||||
row[i] = indexer[runes[i]] ?? 0;
|
||||
}
|
||||
return row;
|
||||
}).toList();
|
||||
|
||||
return {'textIds': textIds, 'textMask': _lengthToMask(lengths)};
|
||||
}
|
||||
|
||||
List<List<List<double>>> _lengthToMask(List<int> lengths, [int? maxLen]) {
|
||||
maxLen ??= lengths.reduce(math.max);
|
||||
return lengths
|
||||
.map((len) => [List.generate(maxLen!, (i) => i < len ? 1.0 : 0.0)])
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Style {
|
||||
final OrtValue ttl, dp;
|
||||
final List<int> ttlShape, dpShape;
|
||||
Style(this.ttl, this.dp, this.ttlShape, this.dpShape);
|
||||
}
|
||||
|
||||
class TextToSpeech {
|
||||
final Map<String, dynamic> cfgs;
|
||||
final UnicodeProcessor textProcessor;
|
||||
final OrtSession dpOrt, textEncOrt, vectorEstOrt, vocoderOrt;
|
||||
final int sampleRate, baseChunkSize, chunkCompressFactor, ldim;
|
||||
|
||||
TextToSpeech(this.cfgs, this.textProcessor, this.dpOrt, this.textEncOrt,
|
||||
this.vectorEstOrt, this.vocoderOrt)
|
||||
: sampleRate = cfgs['ae']['sample_rate'],
|
||||
baseChunkSize = cfgs['ae']['base_chunk_size'],
|
||||
chunkCompressFactor = cfgs['ttl']['chunk_compress_factor'],
|
||||
ldim = cfgs['ttl']['latent_dim'];
|
||||
|
||||
Future<Map<String, dynamic>> call(
|
||||
String text, String lang, Style style, int totalStep,
|
||||
{double speed = 1.05, double silenceDuration = 0.3}) async {
|
||||
final maxLen = lang == 'ko' ? 120 : 300;
|
||||
final chunks = _chunkText(text, maxLen: maxLen);
|
||||
final langList = List.filled(chunks.length, lang);
|
||||
List<double>? wavCat;
|
||||
double durCat = 0;
|
||||
|
||||
for (var i = 0; i < chunks.length; i++) {
|
||||
final result = await _infer([chunks[i]], [langList[i]], style, totalStep,
|
||||
speed: speed);
|
||||
final wav = _safeCast<double>(result['wav']);
|
||||
final duration = _safeCast<double>(result['duration']);
|
||||
|
||||
if (wavCat == null) {
|
||||
wavCat = wav;
|
||||
durCat = duration[0];
|
||||
} else {
|
||||
wavCat = [
|
||||
...wavCat,
|
||||
...List<double>.filled((silenceDuration * sampleRate).floor(), 0.0),
|
||||
...wav
|
||||
];
|
||||
durCat += duration[0] + silenceDuration;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'wav': wavCat,
|
||||
'duration': [durCat]
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _infer(
|
||||
List<String> textList, List<String> langList, Style style, int totalStep,
|
||||
{double speed = 1.05}) async {
|
||||
final bsz = textList.length;
|
||||
final result = textProcessor.call(textList, langList);
|
||||
|
||||
final textIdsRaw = result['textIds'];
|
||||
final textIds = textIdsRaw is List<List<int>>
|
||||
? textIdsRaw
|
||||
: (textIdsRaw as List).map((row) => (row as List).cast<int>()).toList();
|
||||
|
||||
final textMaskRaw = result['textMask'];
|
||||
final textMask = textMaskRaw is List<List<List<double>>>
|
||||
? textMaskRaw
|
||||
: (textMaskRaw as List)
|
||||
.map((batch) => (batch as List)
|
||||
.map((row) => (row as List).cast<double>())
|
||||
.toList())
|
||||
.toList();
|
||||
|
||||
final textIdsShape = [bsz, textIds[0].length];
|
||||
final textMaskShape = [bsz, 1, textMask[0][0].length];
|
||||
final textMaskTensor = await _toTensor(textMask, textMaskShape);
|
||||
|
||||
final dpResult = await dpOrt.run({
|
||||
'text_ids': await _intToTensor(textIds, textIdsShape),
|
||||
'style_dp': style.dp,
|
||||
'text_mask': textMaskTensor,
|
||||
});
|
||||
final durOnnx = _safeCast<double>(await dpResult.values.first.asList());
|
||||
final scaledDur = durOnnx.map((d) => d / speed).toList();
|
||||
|
||||
final textEncResult = await textEncOrt.run({
|
||||
'text_ids': await _intToTensor(textIds, textIdsShape),
|
||||
'style_ttl': style.ttl,
|
||||
'text_mask': textMaskTensor,
|
||||
});
|
||||
|
||||
final latentData = _sampleNoisyLatent(scaledDur);
|
||||
final noisyLatentRaw = latentData['noisyLatent'];
|
||||
var noisyLatent = noisyLatentRaw is List<List<List<double>>>
|
||||
? noisyLatentRaw
|
||||
: (noisyLatentRaw as List)
|
||||
.map((batch) => (batch as List)
|
||||
.map((row) => (row as List).cast<double>())
|
||||
.toList())
|
||||
.toList();
|
||||
|
||||
final latentMaskRaw = latentData['latentMask'];
|
||||
final latentMask = latentMaskRaw is List<List<List<double>>>
|
||||
? latentMaskRaw
|
||||
: (latentMaskRaw as List)
|
||||
.map((batch) => (batch as List)
|
||||
.map((row) => (row as List).cast<double>())
|
||||
.toList())
|
||||
.toList();
|
||||
|
||||
final latentShape = [bsz, noisyLatent[0].length, noisyLatent[0][0].length];
|
||||
final latentMaskTensor =
|
||||
await _toTensor(latentMask, [bsz, 1, latentMask[0][0].length]);
|
||||
|
||||
final totalStepTensor =
|
||||
await _scalarToTensor(List.filled(bsz, totalStep.toDouble()), [bsz]);
|
||||
|
||||
// Denoising loop
|
||||
for (var step = 0; step < totalStep; step++) {
|
||||
final result = await vectorEstOrt.run({
|
||||
'noisy_latent': await _toTensor(noisyLatent, latentShape),
|
||||
'text_emb': textEncResult.values.first,
|
||||
'style_ttl': style.ttl,
|
||||
'text_mask': textMaskTensor,
|
||||
'latent_mask': latentMaskTensor,
|
||||
'total_step': totalStepTensor,
|
||||
'current_step':
|
||||
await _scalarToTensor(List.filled(bsz, step.toDouble()), [bsz]),
|
||||
});
|
||||
|
||||
final denoisedRaw = await result.values.first.asList();
|
||||
final denoised = denoisedRaw is List<double>
|
||||
? denoisedRaw
|
||||
: _safeCast<double>(denoisedRaw);
|
||||
var idx = 0;
|
||||
for (var b = 0; b < noisyLatent.length; b++) {
|
||||
for (var d = 0; d < noisyLatent[b].length; d++) {
|
||||
for (var t = 0; t < noisyLatent[b][d].length; t++) {
|
||||
noisyLatent[b][d][t] = denoised[idx++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final vocoderResult = await vocoderOrt
|
||||
.run({'latent': await _toTensor(noisyLatent, latentShape)});
|
||||
final wavRaw = await vocoderResult.values.first.asList();
|
||||
final wav = wavRaw is List<double> ? wavRaw : _safeCast<double>(wavRaw);
|
||||
|
||||
return {'wav': wav, 'duration': scaledDur};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _sampleNoisyLatent(List<double> duration) {
|
||||
final wavLenMax = duration.reduce(math.max) * sampleRate;
|
||||
final wavLengths = duration.map((d) => (d * sampleRate).floor()).toList();
|
||||
final chunkSize = baseChunkSize * chunkCompressFactor;
|
||||
final latentLen = ((wavLenMax + chunkSize - 1) / chunkSize).floor();
|
||||
final latentDim = ldim * chunkCompressFactor;
|
||||
|
||||
final random = math.Random();
|
||||
final noisyLatent = List.generate(
|
||||
duration.length,
|
||||
(_) => List.generate(
|
||||
latentDim,
|
||||
(_) => List.generate(latentLen, (_) {
|
||||
final u1 = math.max(1e-10, random.nextDouble());
|
||||
final u2 = random.nextDouble();
|
||||
return math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
final latentMask = _getLatentMask(wavLengths);
|
||||
|
||||
for (var b = 0; b < noisyLatent.length; b++) {
|
||||
for (var d = 0; d < noisyLatent[b].length; d++) {
|
||||
for (var t = 0; t < noisyLatent[b][d].length; t++) {
|
||||
noisyLatent[b][d][t] *= latentMask[b][0][t];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {'noisyLatent': noisyLatent, 'latentMask': latentMask};
|
||||
}
|
||||
|
||||
List<List<List<double>>> _getLatentMask(List<int> wavLengths) {
|
||||
final latentSize = baseChunkSize * chunkCompressFactor;
|
||||
final latentLengths = wavLengths
|
||||
.map((len) => ((len + latentSize - 1) / latentSize).floor())
|
||||
.toList();
|
||||
final maxLen = latentLengths.reduce(math.max);
|
||||
return latentLengths
|
||||
.map((len) => [List.generate(maxLen, (i) => i < len ? 1.0 : 0.0)])
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> _chunkText(String text, {int maxLen = 300}) {
|
||||
final paragraphs = text
|
||||
.trim()
|
||||
.split(RegExp(r'\n\s*\n+'))
|
||||
.where((p) => p.trim().isNotEmpty)
|
||||
.toList();
|
||||
|
||||
final chunks = <String>[];
|
||||
for (var paragraph in paragraphs) {
|
||||
paragraph = paragraph.trim();
|
||||
if (paragraph.isEmpty) continue;
|
||||
|
||||
final sentences = paragraph.split(RegExp(
|
||||
r'(?<!Mr\.|Mrs\.|Ms\.|Dr\.|Prof\.)(?<!\b[A-Z]\.)(?<=[.!?])\s+'));
|
||||
|
||||
var currentChunk = '';
|
||||
for (final sentence in sentences) {
|
||||
if (currentChunk.length + sentence.length + 1 <= maxLen) {
|
||||
currentChunk += (currentChunk.isNotEmpty ? ' ' : '') + sentence;
|
||||
} else {
|
||||
if (currentChunk.isNotEmpty) chunks.add(currentChunk.trim());
|
||||
currentChunk = sentence;
|
||||
}
|
||||
}
|
||||
if (currentChunk.isNotEmpty) chunks.add(currentChunk.trim());
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
List<T> _safeCast<T>(dynamic raw) {
|
||||
if (raw is List<T>) return raw;
|
||||
if (raw is List) {
|
||||
if (raw.isNotEmpty && raw.first is List) {
|
||||
return _flattenList<T>(raw);
|
||||
}
|
||||
if (T == double) {
|
||||
return raw
|
||||
.map((e) => e is num ? e.toDouble() : double.parse(e.toString()))
|
||||
.toList() as List<T>;
|
||||
}
|
||||
return raw.cast<T>();
|
||||
}
|
||||
throw Exception('Cannot convert $raw to List<$T>');
|
||||
}
|
||||
|
||||
List<T> _flattenList<T>(dynamic list) {
|
||||
if (list is List) {
|
||||
return list.expand((e) => _flattenList<T>(e)).toList();
|
||||
}
|
||||
if (T == double && list is num) {
|
||||
return [list.toDouble()] as List<T>;
|
||||
}
|
||||
return [list as T];
|
||||
}
|
||||
|
||||
Future<OrtValue> _toTensor(dynamic array, List<int> dims) async {
|
||||
final flat = _flattenList<double>(array);
|
||||
return await OrtValue.fromList(Float32List.fromList(flat), dims);
|
||||
}
|
||||
|
||||
Future<OrtValue> _scalarToTensor(List<double> array, List<int> dims) async {
|
||||
return await OrtValue.fromList(Float32List.fromList(array), dims);
|
||||
}
|
||||
|
||||
Future<OrtValue> _intToTensor(List<List<int>> array, List<int> dims) async {
|
||||
final flat = array.expand((row) => row).toList();
|
||||
return await OrtValue.fromList(Int64List.fromList(flat), dims);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TextToSpeech> loadTextToSpeech(String onnxDir,
|
||||
{bool useGpu = false}) async {
|
||||
if (useGpu) throw Exception('GPU mode not supported yet');
|
||||
|
||||
logger.i('Loading TTS models from $onnxDir');
|
||||
|
||||
final cfgs = await _loadCfgs(onnxDir);
|
||||
final sessions = await _loadOnnxAll(onnxDir);
|
||||
final textProcessor =
|
||||
await UnicodeProcessor.load('$onnxDir/unicode_indexer.json');
|
||||
|
||||
logger.i('TTS models loaded successfully');
|
||||
|
||||
return TextToSpeech(
|
||||
cfgs,
|
||||
textProcessor,
|
||||
sessions['dpOrt']!,
|
||||
sessions['textEncOrt']!,
|
||||
sessions['vectorEstOrt']!,
|
||||
sessions['vocoderOrt']!,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Style> loadVoiceStyle(List<String> paths) async {
|
||||
final bsz = paths.length;
|
||||
|
||||
final firstJson = jsonDecode(
|
||||
paths[0].startsWith('assets/')
|
||||
? await rootBundle.loadString(paths[0])
|
||||
: File(paths[0]).readAsStringSync(),
|
||||
);
|
||||
|
||||
final ttlDims = List<int>.from(firstJson['style_ttl']['dims']);
|
||||
final dpDims = List<int>.from(firstJson['style_dp']['dims']);
|
||||
|
||||
final ttlFlat = Float32List(bsz * ttlDims[1] * ttlDims[2]);
|
||||
final dpFlat = Float32List(bsz * dpDims[1] * dpDims[2]);
|
||||
|
||||
for (var i = 0; i < bsz; i++) {
|
||||
final json = jsonDecode(
|
||||
paths[i].startsWith('assets/')
|
||||
? await rootBundle.loadString(paths[i])
|
||||
: File(paths[i]).readAsStringSync(),
|
||||
);
|
||||
|
||||
final ttlData = _flattenToDouble(json['style_ttl']['data']);
|
||||
final dpData = _flattenToDouble(json['style_dp']['data']);
|
||||
|
||||
ttlFlat.setRange(i * ttlDims[1] * ttlDims[2],
|
||||
(i + 1) * ttlDims[1] * ttlDims[2], ttlData);
|
||||
dpFlat.setRange(
|
||||
i * dpDims[1] * dpDims[2], (i + 1) * dpDims[1] * dpDims[2], dpData);
|
||||
}
|
||||
|
||||
final ttlShape = [bsz, ttlDims[1], ttlDims[2]];
|
||||
final dpShape = [bsz, dpDims[1], dpDims[2]];
|
||||
|
||||
return Style(
|
||||
await OrtValue.fromList(ttlFlat, ttlShape),
|
||||
await OrtValue.fromList(dpFlat, dpShape),
|
||||
ttlShape,
|
||||
dpShape,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _loadCfgs(String onnxDir) async {
|
||||
final path = '$onnxDir/tts.json';
|
||||
final json = jsonDecode(await rootBundle.loadString(path));
|
||||
return json as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<String> copyModelToFile(String path) async {
|
||||
final byteData = await rootBundle.load(path);
|
||||
final tempDir = await getApplicationCacheDirectory();
|
||||
final modelPath = '${tempDir.path}/${path.split("/").last}';
|
||||
|
||||
final file = File(modelPath);
|
||||
await file.writeAsBytes(byteData.buffer.asUint8List());
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
Future<Map<String, OrtSession>> _loadOnnxAll(String dir) async {
|
||||
final ort = OnnxRuntime();
|
||||
final models = [
|
||||
'duration_predictor',
|
||||
'text_encoder',
|
||||
'vector_estimator',
|
||||
'vocoder'
|
||||
];
|
||||
|
||||
final sessions = await Future.wait(models.map((name) async {
|
||||
final path = await copyModelToFile('$dir/$name.onnx');
|
||||
logger.d('Loading $name.onnx');
|
||||
return ort.createSessionFromAsset(path);
|
||||
}));
|
||||
|
||||
return {
|
||||
'dpOrt': sessions[0],
|
||||
'textEncOrt': sessions[1],
|
||||
'vectorEstOrt': sessions[2],
|
||||
'vocoderOrt': sessions[3],
|
||||
};
|
||||
}
|
||||
|
||||
List<double> _flattenToDouble(dynamic list) {
|
||||
if (list is List) return list.expand((e) => _flattenToDouble(e)).toList();
|
||||
return [list is num ? list.toDouble() : double.parse(list.toString())];
|
||||
}
|
||||
|
||||
void writeWavFile(String filename, List<double> audioData, int sampleRate) {
|
||||
const numChannels = 1;
|
||||
const bitsPerSample = 16;
|
||||
final dataSize = audioData.length * 2;
|
||||
|
||||
final buffer = ByteData(44 + dataSize);
|
||||
var offset = 0;
|
||||
|
||||
// RIFF header
|
||||
for (var byte in [0x52, 0x49, 0x46, 0x46]) {
|
||||
buffer.setUint8(offset++, byte);
|
||||
}
|
||||
buffer.setUint32(offset, 36 + dataSize, Endian.little);
|
||||
offset += 4;
|
||||
|
||||
// WAVE
|
||||
for (var byte in [0x57, 0x41, 0x56, 0x45]) {
|
||||
buffer.setUint8(offset++, byte);
|
||||
}
|
||||
|
||||
// fmt chunk
|
||||
for (var byte in [0x66, 0x6D, 0x74, 0x20]) {
|
||||
buffer.setUint8(offset++, byte);
|
||||
}
|
||||
buffer.setUint32(offset, 16, Endian.little);
|
||||
offset += 4;
|
||||
buffer.setUint16(offset, 1, Endian.little);
|
||||
offset += 2;
|
||||
buffer.setUint16(offset, numChannels, Endian.little);
|
||||
offset += 2;
|
||||
buffer.setUint32(offset, sampleRate, Endian.little);
|
||||
offset += 4;
|
||||
buffer.setUint32(offset, sampleRate * numChannels * 2, Endian.little);
|
||||
offset += 4;
|
||||
buffer.setUint16(offset, numChannels * 2, Endian.little);
|
||||
offset += 2;
|
||||
buffer.setUint16(offset, bitsPerSample, Endian.little);
|
||||
offset += 2;
|
||||
|
||||
// data chunk
|
||||
for (var byte in [0x64, 0x61, 0x74, 0x61]) {
|
||||
buffer.setUint8(offset++, byte);
|
||||
}
|
||||
buffer.setUint32(offset, dataSize, Endian.little);
|
||||
offset += 4;
|
||||
|
||||
// Write audio samples
|
||||
for (var i = 0; i < audioData.length; i++) {
|
||||
final sample = (audioData[i].clamp(-1.0, 1.0) * 32767).round();
|
||||
buffer.setInt16(offset + i * 2, sample, Endian.little);
|
||||
}
|
||||
|
||||
File(filename).writeAsBytesSync(buffer.buffer.asUint8List());
|
||||
}
|
||||
391
flutter/lib/main.dart
Normal file
@@ -0,0 +1,391 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_sdk/helper.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const SupertonicApp());
|
||||
}
|
||||
|
||||
class SupertonicApp extends StatelessWidget {
|
||||
const SupertonicApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Supertonic 2',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const TTSPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TTSPage extends StatefulWidget {
|
||||
const TTSPage({super.key});
|
||||
|
||||
@override
|
||||
State<TTSPage> createState() => _TTSPageState();
|
||||
}
|
||||
|
||||
class _TTSPageState extends State<TTSPage> {
|
||||
final TextEditingController _textController = TextEditingController(
|
||||
text: 'Hello, this is a text to speech example.',
|
||||
);
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
|
||||
TextToSpeech? _textToSpeech;
|
||||
Style? _style;
|
||||
bool _isLoading = false;
|
||||
bool _isGenerating = false;
|
||||
String _status = 'Not initialized';
|
||||
int _totalSteps = 5;
|
||||
double _speed = 1.05;
|
||||
String _selectedLang = 'en';
|
||||
bool _isPlaying = false;
|
||||
String? _lastGeneratedFilePath;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadModels();
|
||||
_setupAudioPlayerListeners();
|
||||
}
|
||||
|
||||
void _setupAudioPlayerListeners() {
|
||||
_audioPlayer.playerStateStream.listen((state) {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_isPlaying = state.playing;
|
||||
|
||||
if (state.processingState == ProcessingState.completed) {
|
||||
_isPlaying = false;
|
||||
_status = 'Ready';
|
||||
} else if (state.processingState == ProcessingState.loading) {
|
||||
_status = 'Loading audio...';
|
||||
} else if (state.processingState == ProcessingState.buffering) {
|
||||
_status = 'Buffering...';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadModels() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_status = 'Loading models...';
|
||||
});
|
||||
|
||||
try {
|
||||
_textToSpeech = await loadTextToSpeech('assets/onnx', useGpu: false);
|
||||
_style = await loadVoiceStyle(['assets/voice_styles/M1.json']);
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_status = 'Ready';
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
logger.e('Error loading models', error: e, stackTrace: stackTrace);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_status = 'Error: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _generateSpeech() async {
|
||||
if (_textToSpeech == null || _style == null) {
|
||||
setState(() => _status = 'Models not loaded yet');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_textController.text.trim().isEmpty) {
|
||||
setState(() => _status = 'Please enter some text');
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isGenerating = true;
|
||||
_status = 'Generating speech...';
|
||||
});
|
||||
|
||||
List<double>? wav;
|
||||
List<double>? duration;
|
||||
|
||||
// Step 1: Generate speech
|
||||
try {
|
||||
final result = await _textToSpeech!.call(
|
||||
_textController.text,
|
||||
_selectedLang,
|
||||
_style!,
|
||||
_totalSteps,
|
||||
speed: _speed,
|
||||
);
|
||||
|
||||
wav = result['wav'] is List<double>
|
||||
? result['wav']
|
||||
: (result['wav'] as List).cast<double>();
|
||||
duration = result['duration'] is List<double>
|
||||
? result['duration']
|
||||
: (result['duration'] as List).cast<double>();
|
||||
} catch (e) {
|
||||
logger.e('Error generating speech', error: e);
|
||||
setState(() {
|
||||
_isGenerating = false;
|
||||
_status = 'Error generating speech: $e';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Save to file and play
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final outputPath = '${tempDir.path}/speech_$timestamp.wav';
|
||||
|
||||
writeWavFile(outputPath, wav!, _textToSpeech!.sampleRate);
|
||||
|
||||
final file = File(outputPath);
|
||||
if (!file.existsSync()) {
|
||||
throw Exception('Failed to create WAV file');
|
||||
}
|
||||
|
||||
final absolutePath = file.absolute.path;
|
||||
|
||||
setState(() {
|
||||
_isGenerating = false;
|
||||
_status = 'Playing ${duration![0].toStringAsFixed(2)}s of audio...';
|
||||
_lastGeneratedFilePath = absolutePath;
|
||||
});
|
||||
|
||||
logger.i('Audio saved to $absolutePath');
|
||||
|
||||
final uri = Uri.file(absolutePath);
|
||||
await _audioPlayer.setAudioSource(AudioSource.uri(uri));
|
||||
await _audioPlayer.play();
|
||||
} catch (e) {
|
||||
logger.e('Error playing audio', error: e);
|
||||
setState(() {
|
||||
_isGenerating = false;
|
||||
_status = 'Error playing audio: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _downloadFile() async {
|
||||
if (_lastGeneratedFilePath == null) return;
|
||||
|
||||
try {
|
||||
final sourceFile = File(_lastGeneratedFilePath!);
|
||||
if (!sourceFile.existsSync()) {
|
||||
setState(() => _status = 'Error: File no longer exists');
|
||||
return;
|
||||
}
|
||||
|
||||
final downloadsDir = await getDownloadsDirectory();
|
||||
if (downloadsDir == null) {
|
||||
setState(() => _status = 'Error: Could not access downloads folder');
|
||||
return;
|
||||
}
|
||||
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final downloadPath = '${downloadsDir.path}/speech_$timestamp.wav';
|
||||
|
||||
await sourceFile.copy(downloadPath);
|
||||
logger.i('File saved to $downloadPath');
|
||||
|
||||
setState(() => _status = 'File saved to: $downloadPath');
|
||||
} catch (e) {
|
||||
logger.e('Error downloading file', error: e);
|
||||
setState(() => _status = 'Error downloading file: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
title: const Text('Supertonic 2'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Status indicator
|
||||
Card(
|
||||
color: _isLoading || _isGenerating
|
||||
? Colors.orange.shade100
|
||||
: _status.startsWith('Error')
|
||||
? Colors.red.shade100
|
||||
: Colors.green.shade100,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (_isLoading || _isGenerating)
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
if (_isLoading || _isGenerating) const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child:
|
||||
Text(_status, style: const TextStyle(fontSize: 16)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Text input
|
||||
TextField(
|
||||
controller: _textController,
|
||||
maxLines: 5,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Text to synthesize',
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Enter the text you want to convert to speech...',
|
||||
),
|
||||
enabled: !_isLoading && !_isGenerating,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Parameters
|
||||
Text('Parameters', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Denoising steps slider
|
||||
Row(
|
||||
children: [
|
||||
const Expanded(flex: 2, child: Text('Denoising Steps:')),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Slider(
|
||||
value: _totalSteps.toDouble(),
|
||||
min: 1,
|
||||
max: 20,
|
||||
divisions: 19,
|
||||
label: _totalSteps.toString(),
|
||||
onChanged: _isLoading || _isGenerating
|
||||
? null
|
||||
: (value) =>
|
||||
setState(() => _totalSteps = value.toInt()),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child:
|
||||
Text(_totalSteps.toString(), textAlign: TextAlign.right),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Speed slider
|
||||
Row(
|
||||
children: [
|
||||
const Expanded(flex: 2, child: Text('Speed:')),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Slider(
|
||||
value: _speed,
|
||||
min: 0.5,
|
||||
max: 2.0,
|
||||
divisions: 30,
|
||||
label: _speed.toStringAsFixed(2),
|
||||
onChanged: _isLoading || _isGenerating
|
||||
? null
|
||||
: (value) => setState(() => _speed = value),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Text(_speed.toStringAsFixed(2),
|
||||
textAlign: TextAlign.right),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Language selector
|
||||
Row(
|
||||
children: [
|
||||
const Expanded(flex: 2, child: Text('Language:')),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: DropdownButton<String>(
|
||||
value: _selectedLang,
|
||||
isExpanded: true,
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'en', child: Text('English')),
|
||||
DropdownMenuItem(value: 'ko', child: Text('한국어')),
|
||||
DropdownMenuItem(value: 'es', child: Text('Español')),
|
||||
DropdownMenuItem(value: 'pt', child: Text('Português')),
|
||||
DropdownMenuItem(value: 'fr', child: Text('Français')),
|
||||
],
|
||||
onChanged: _isLoading || _isGenerating
|
||||
? null
|
||||
: (value) => setState(() => _selectedLang = value!),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Generate button
|
||||
ElevatedButton.icon(
|
||||
onPressed: _isLoading || _isGenerating
|
||||
? null
|
||||
: _isPlaying
|
||||
? () async {
|
||||
await _audioPlayer.stop();
|
||||
setState(() => _status = 'Ready');
|
||||
}
|
||||
: _generateSpeech,
|
||||
icon: Icon(_isPlaying ? Icons.stop : Icons.play_arrow),
|
||||
label: Text(
|
||||
_isGenerating
|
||||
? 'Generating...'
|
||||
: _isPlaying
|
||||
? 'Stop Playback'
|
||||
: 'Generate & Play Speech',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
|
||||
// Download button
|
||||
if (_lastGeneratedFilePath != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _isLoading || _isGenerating ? null : _downloadFile,
|
||||
icon: const Icon(Icons.download),
|
||||
label: const Text('Download WAV File',
|
||||
style: TextStyle(fontSize: 16)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
7
flutter/macos/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Flutter-related
|
||||
**/Flutter/ephemeral/
|
||||
**/Pods/
|
||||
|
||||
# Xcode-related
|
||||
**/dgph
|
||||
**/xcuserdata/
|
||||
2
flutter/macos/Flutter/Flutter-Debug.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
2
flutter/macos/Flutter/Flutter-Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
16
flutter/macos/Flutter/GeneratedPluginRegistrant.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import audio_session
|
||||
import flutter_onnxruntime
|
||||
import just_audio
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||
FlutterOnnxruntimePlugin.register(with: registry.registrar(forPlugin: "FlutterOnnxruntimePlugin"))
|
||||
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||
}
|
||||
45
flutter/macos/Podfile
Normal file
@@ -0,0 +1,45 @@
|
||||
platform :osx, '14.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks! :linkage => :static
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '14.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
54
flutter/macos/Podfile.lock
Normal file
@@ -0,0 +1,54 @@
|
||||
PODS:
|
||||
- audio_session (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_onnxruntime (0.0.1):
|
||||
- FlutterMacOS
|
||||
- onnxruntime-objc (= 1.21.0)
|
||||
- FlutterMacOS (1.0.0)
|
||||
- just_audio (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- objective_c (0.0.1):
|
||||
- FlutterMacOS
|
||||
- onnxruntime-c (1.21.0)
|
||||
- onnxruntime-objc (1.21.0):
|
||||
- onnxruntime-objc/Core (= 1.21.0)
|
||||
- onnxruntime-objc/Core (1.21.0):
|
||||
- onnxruntime-c (= 1.21.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
|
||||
- flutter_onnxruntime (from `Flutter/ephemeral/.symlinks/plugins/flutter_onnxruntime/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/darwin`)
|
||||
- objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- onnxruntime-c
|
||||
- onnxruntime-objc
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
audio_session:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
|
||||
flutter_onnxruntime:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_onnxruntime/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
just_audio:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/just_audio/darwin
|
||||
objective_c:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/objective_c/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
audio_session: 728ae3823d914f809c485d390274861a24b0904e
|
||||
flutter_onnxruntime: e6887abc1032d3e5c92f84b912ad42c33e9ce1c9
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79
|
||||
objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067
|
||||
onnxruntime-c: ac65025f01072d25d7d394a2b43ac30d9397b260
|
||||
onnxruntime-objc: 5fa03134356d47b642ec85b1023d9907a123d201
|
||||
|
||||
PODFILE CHECKSUM: 6b8e7008b8bf73cd361b3ffb8aa3768b71e74409
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
13
flutter/macos/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_64.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "app_icon_1024.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 520 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
343
flutter/macos/Runner/Base.lproj/MainMenu.xib
Normal file
@@ -0,0 +1,343 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||
<connections>
|
||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||
<menuItem title="Find" id="4EN-yA-p0u">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||
<items>
|
||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||
<items>
|
||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||
<items>
|
||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="EPT-qC-fAb">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="142" y="-258"/>
|
||||
</menu>
|
||||
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
14
flutter/macos/Runner/Configs/AppInfo.xcconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
// Application-level settings for the Runner target.
|
||||
//
|
||||
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
||||
// future. If not, the values below would default to using the project name when this becomes a
|
||||
// 'flutter create' template.
|
||||
|
||||
// The application's name. By default this is also the title of the Flutter window.
|
||||
PRODUCT_NAME = flutter_sdk
|
||||
|
||||
// The application's bundle identifier
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterSdk
|
||||
|
||||
// The copyright displayed in application information
|
||||
PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.
|
||||
2
flutter/macos/Runner/Configs/Debug.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
2
flutter/macos/Runner/Configs/Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||
#include "Warnings.xcconfig"
|
||||
13
flutter/macos/Runner/Configs/Warnings.xcconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES
|
||||
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||
CLANG_WARN_PRAGMA_PACK = YES
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES
|
||||
CLANG_WARN_COMMA = YES
|
||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
||||
GCC_WARN_SHADOW = YES
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES
|
||||
12
flutter/macos/Runner/DebugProfile.entitlements
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
32
flutter/macos/Runner/Info.plist
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
15
flutter/macos/Runner/MainFlutterWindow.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
class MainFlutterWindow: NSWindow {
|
||||
override func awakeFromNib() {
|
||||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
super.awakeFromNib()
|
||||
}
|
||||
}
|
||||
8
flutter/macos/Runner/Release.entitlements
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
12
flutter/macos/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
418
flutter/pubspec.lock
Normal file
@@ -0,0 +1,418 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
audio_session:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_session
|
||||
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_onnxruntime:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_onnxruntime
|
||||
sha256: "55842e69293ec52c07f3065049ff7641e94e8a6cca3f659a913d5401a3994424"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
just_audio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: just_audio
|
||||
sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.5"
|
||||
just_audio_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: just_audio_platform_interface
|
||||
sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.0"
|
||||
just_audio_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: just_audio_web
|
||||
sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "1f81ed9e41909d44162d7ec8663b2c647c202317cc0b56d3d56f6a13146a0b64"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6192e477f34018ef1ea790c56fffc7302e3bc3efede9e798b934c252c8c105ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
26
flutter/pubspec.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
name: flutter_sdk
|
||||
description: Supertonic Flutter SDK TTS Example
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_onnxruntime: ^1.6.0
|
||||
args: ^2.4.0
|
||||
path_provider: ^2.1.1
|
||||
just_audio: ^0.10.5
|
||||
logger: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
flutter:
|
||||
assets:
|
||||
- assets/onnx/
|
||||
- assets/voice_styles/
|
||||
uses-material-design: true
|
||||