diff --git a/docs/editor-dev.md b/docs/editor-dev.md index 1d6988e..6cc943a 100644 --- a/docs/editor-dev.md +++ b/docs/editor-dev.md @@ -20,7 +20,7 @@ You'll need: Here is a list of packages for Ubuntu-like systems: ``` -sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev +sudo apt install cmake mesa-utils mesa-common-dev ninja-build libxext-dev libpipewire-0.3-dev libasound2-dev libpulse-dev ``` ## How to build diff --git a/docs/player-dev.md b/docs/player-dev.md new file mode 100644 index 0000000..0798775 --- /dev/null +++ b/docs/player-dev.md @@ -0,0 +1,21 @@ +# Story player project + +The Story Player is a Flutter application. + +# Packages + +sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + +# Environment variables + + + +# Build for Linux + +flutter build linux + +# Build for Android + + +flutter build apk + diff --git a/story-player/.gitignore b/story-player/.gitignore index 761717e..59653c0 100644 --- a/story-player/.gitignore +++ b/story-player/.gitignore @@ -31,6 +31,7 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +/linux/flutter/generated_plugin* # Symbolication related app.*.symbols diff --git a/story-player/android/app/build.gradle b/story-player/android/app/build.gradle index f53222a..19ca63c 100644 --- a/story-player/android/app/build.gradle +++ b/story-player/android/app/build.gradle @@ -61,6 +61,12 @@ android { signingConfig signingConfigs.debug } } + + externalNativeBuild { + cmake { + path "../../storyvm/CMakeLists.txt" + } + } } flutter { diff --git a/story-player/lib/libstory/indexfile.dart b/story-player/lib/libstory/indexfile.dart new file mode 100644 index 0000000..55d7574 --- /dev/null +++ b/story-player/lib/libstory/indexfile.dart @@ -0,0 +1,175 @@ +library libstory; + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:logger/logger.dart'; + +var logger = Logger(printer: PrettyPrinter(methodCount: 0)); + +class IndexFile { + static const int tlvArrayType = 0xAB; + static const int tlvObjectType = 0xE7; + static const int tlvIntegerType = 0x77; + static const int tlvStringType = 0x3D; + static const int tlvRealType = 0xB8; + + // Index File stuff + Uint8List indexFileBuffer = Uint8List(0); + ByteData indexFileStream = ByteData(0); + int readPtr = 0; + int indexFileVersion = 0; + bool indexFileIsValid = false; + + // Variables de gestion de l'index des histoires + int storiesCount = 0; + String libraryPath = ''; + int currentStoryIndex = 0; + + List< + ({ + String uuid, + String titleImage, + String titleSound, + String title, + String description, + int version + })> stories = []; + + String getCurrentTitleImage() { + String fileName = ''; + if (currentStoryIndex < storiesCount) { + fileName = + '$libraryPath/${stories[currentStoryIndex].uuid}/assets/${stories[currentStoryIndex].titleImage}'; + } + return fileName; + } + + String getCurrentSoundImage() { + String fileName = ''; + if (currentStoryIndex < storiesCount) { + fileName = + '$libraryPath/${stories[currentStoryIndex].uuid}/assets/${stories[currentStoryIndex].titleSound}'; + } + return fileName; + } + + String getCurrentStoryPath() { + String path = ''; + if (currentStoryIndex < storiesCount) { + path = + '$libraryPath/${stories[currentStoryIndex].uuid}'; + } + return path; + } + + void next() { + currentStoryIndex++; + if (currentStoryIndex >= storiesCount) { + currentStoryIndex = 0; + } + } + + void previous() { + currentStoryIndex--; + if (currentStoryIndex < 0) { + currentStoryIndex = storiesCount - 1; + } + } + + // Returns the size, 0 if error + int getTl(int expectedType) { + int size = 0; + if (indexFileStream.getUint8(readPtr) == expectedType) { + readPtr++; + size = indexFileStream.getUint16(readPtr, Endian.little); + readPtr += 2; + } else { + throw Exception("Expected type: $expectedType"); + } + return size; + } + + int getIntegerValue() { + int size = getTl(tlvIntegerType); + + if (size == 4) { + int value = indexFileStream.getUint32(readPtr, Endian.little); + readPtr += 4; + return value; + } else { + throw Exception("Expected an integer of size 4 bytes"); + } + } + + String getStringValue() { + int size = getTl(tlvStringType); + if (size > 0) { + String value = String.fromCharCodes( + indexFileStream.buffer.asUint8List(), readPtr, readPtr + size); + readPtr += size; + return value; + } else { + return ""; + } + } + + Future loadIndexFile(String libraryRoot) async { + libraryPath = libraryRoot; + indexFileIsValid = false; + final file = File('$libraryRoot/index.ost'); + if (!await file.exists()) { + logger.d('Le fichier n\'existe pas.'); + return false; + } + + // Ouvrir le fichier en mode lecture binaire + indexFileBuffer = file.readAsBytesSync(); + readPtr = 0; + indexFileStream = + ByteData.sublistView(indexFileBuffer, readPtr); // start at zero + stories.clear(); + + if (indexFileBuffer.lengthInBytes > 3) { + // Root must be an object containing 2 elements + + try { + if (getTl(tlvObjectType) == 2) { + indexFileVersion = getIntegerValue(); + storiesCount = getTl(tlvArrayType); + + for (int i = 0; i < storiesCount; i++) { + if (getTl(tlvObjectType) == 6) { + var record = ( + uuid: getStringValue(), + titleImage: getStringValue(), + titleSound: getStringValue(), + title: getStringValue(), + description: getStringValue(), + version: getIntegerValue() + ); + + logger.d('Found story: ${record.title.toString()}'); + + stories.add(record); + } else { + throw Exception("Expected object of 6 elements at root"); + } + } + + // If get through here, no exception raised so the file shoould be ok + indexFileIsValid = true; + currentStoryIndex = 0; + return true; + } else { + throw Exception("Expected object of 2 elements at root"); + } + } catch (e) { + logger.e('Exception reading index file: $e'); + } finally {} + } + return false; + } + + +} diff --git a/story-player/lib/libstory/storyvm.dart b/story-player/lib/libstory/storyvm.dart new file mode 100644 index 0000000..389397a --- /dev/null +++ b/story-player/lib/libstory/storyvm.dart @@ -0,0 +1,164 @@ +library libstory; + +import 'dart:ffi'; +import 'dart:io'; +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; +import 'package:logger/logger.dart'; +import 'package:event_bus/event_bus.dart'; + +var logger = Logger(printer: PrettyPrinter(methodCount: 0)); + +/// The global [EventBus] object. +EventBus eventBus = EventBus(); + +class MediaEvent { + String image; + String sound; + + MediaEvent(this.image, this.sound); +} + +Completer periodic(Duration interval, Function(int cycle) callback) { + final done = Completer(); + () async { + var cycle = 0; + while (!done.isCompleted) { + try { + await callback(cycle); + } catch (e, s) { + logger.e("$e", stackTrace: s); + } + cycle++; + await done.future.timeout(interval).onError((error, stackTrace) => false); + } + }(); + return done; +} + +typedef MediaCallbackType = Void Function(Int32 i, Pointer str); +typedef MediaCallback = void Function(Int32 i, Pointer str); + +typedef VmInitializeType = Void Function( + Pointer>); +typedef VmInitialize = void Function( + Pointer>); + +typedef VmStartType = Void Function(Pointer data, Uint32 size); +typedef VmStart = void Function(Pointer data, int size); + +typedef VmRunType = Void Function(); +typedef VmRun = void Function(); + +typedef VmSendEventType = Void Function(Int); +typedef VmSendEvent = void Function(int event); + +enum VmEvent { evNoEvent, evStep, evOkButton, evPreviousButton, evNextButton, evAudioFinished, evStop } + +class StoryVm { + static late DynamicLibrary nativeApiLib; + static late VmInitialize vmInitialize; + static late VmStart vmStart; + static late VmRun vmRun; + static late VmSendEvent vmSendEvent; + static String currentStoryPath = ''; + + static bool running = false; + + static bool loadLibrary() { + String dllName = 'libstoryvm.so'; + + if (Platform.isMacOS) { + dllName = 'libstoryvm.dylib'; + } else if (Platform.isWindows) { + dllName = 'storyvm.dll'; + } + + final dylib = DynamicLibrary.open(dllName); + + vmInitialize = dylib + .lookup>('storyvm_initialize') + .asFunction(); + + vmStart = dylib.lookup>('storyvm_start').asFunction(); + + vmRun = dylib.lookup>('storyvm_run').asFunction(); + + vmSendEvent = dylib.lookup>('storyvm_send_event').asFunction(); + + return true; + } + + static void mediaCallback(int i, Pointer str) { + String file = str.toDartString(); + logger.d('Mediatype: $i, Media file: $file'); + + if (i == 0) { + eventBus.fire(MediaEvent(file, "")); + } else { + eventBus.fire(MediaEvent("", file)); + } + + } + + static void initialize() { + vmInitialize(Pointer.fromFunction(mediaCallback)); + } + + static Completer task = Completer(); + + static Future start(String storyBasePath) async{ + currentStoryPath = storyBasePath; + final file = File('$storyBasePath/story.c32'); + if (!file.existsSync()) { + logger.d('Le fichier n\'existe pas.'); + return false; + } + + // Ouvrir le fichier en mode lecture binaire + Uint8List fileBuffer = file.readAsBytesSync(); + + Pointer dataPointer = malloc.allocate(fileBuffer.length); + for (int i = 0; i < fileBuffer.length; i++) { + dataPointer[i] = fileBuffer[i]; + } + + vmStart(dataPointer, fileBuffer.length); + running = true; + task = periodic(const Duration(milliseconds: 10), (cycle) async { + if (running) { + vmRun(); + } + }); + + return true; + } + + static void endOfSound() { + + vmSendEvent(VmEvent.evAudioFinished.index); + } + + static void okButton() { + + vmSendEvent(VmEvent.evOkButton.index); + } + + static void previousButton() { + + vmSendEvent(VmEvent.evPreviousButton.index); + } + + static void nextButton() { + + vmSendEvent(VmEvent.evNextButton.index); + } + + static void stop() { + logger.d('VM stop'); + running = false; + task.complete(true); + } +} diff --git a/story-player/lib/main.dart b/story-player/lib/main.dart index 539aa2b..2847c9b 100644 --- a/story-player/lib/main.dart +++ b/story-player/lib/main.dart @@ -9,13 +9,14 @@ import 'package:shelf/shelf_io.dart' as io; import 'package:shelf_router/shelf_router.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart' as p; -import 'package:http_parser/http_parser.dart'; +import 'package:http_parser/http_parser.dart'; // y'a un warning mais il faut laisser cet import import 'package:saf/saf.dart'; import 'package:path_provider/path_provider.dart'; - +import 'package:audioplayers/audioplayers.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:flutter_launcher/storyvm.dart'; +import 'libstory/storyvm.dart'; +import 'libstory/indexfile.dart'; import 'package:logger/logger.dart'; @@ -112,7 +113,7 @@ Future printIps() async { void main() { StoryVm.loadLibrary(); StoryVm.initialize(); - StoryVm.start(); + udpServer(); httpServer(); printIps(); @@ -170,9 +171,16 @@ class MyHomePage extends StatefulWidget { State createState() => _MyHomePageState(); } +enum PlayerState { disabled, indexFile, inStory } + class _MyHomePageState extends State { - int _counter = 0; String myPath = 'fffff'; + IndexFile indexFile = IndexFile(); + String currentImage = 'assets/320x240.png'; + final player = AudioPlayer(); + StreamSubscription? mediaPub; + PlayerState state = PlayerState.disabled; + StreamSubscription? audioPlayerSub; void initPaths() async { Directory? dir; @@ -191,23 +199,42 @@ class _MyHomePageState extends State { _MyHomePageState() { initPaths(); + mediaPub = eventBus.on().listen((event) { + setState(() { + if (event.image.isNotEmpty) { + currentImage = '${indexFile.getCurrentStoryPath()}/assets/${event.image}'; + } + }); + + if (event.sound.isNotEmpty) { + player.play(DeviceFileSource('${indexFile.getCurrentStoryPath()}/assets/${event.sound}')); + } + }); + + audioPlayerSub = player.onPlayerComplete.listen((event) { + if (state == PlayerState.inStory) { + // Send end of music event + StoryVm.endOfSound(); + } + }); + + state = PlayerState.indexFile; } - void _incrementCounter() { + void showCurrentStory() async { setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; + currentImage = indexFile.getCurrentTitleImage(); + logger.d('Current image: $currentImage'); }); + + var asset = DeviceFileSource(indexFile.getCurrentSoundImage()); + logger.d('Asset: ${asset.toString()}'); + await player.play(asset); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. + // This method is rerun every time setState is called, for instance // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather @@ -233,18 +260,11 @@ class _MyHomePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), Text( myPath, style: Theme.of(context).textTheme.headlineMedium, ), - const Image(image: AssetImage('assets/320x240.png')), + Image(image: AssetImage(currentImage)), ], ), ), @@ -253,7 +273,7 @@ class _MyHomePageState extends State { child: Row( children: [ IconButton( - tooltip: 'Open navigation menu', + tooltip: 'Select library directory', icon: const Icon( Icons.folder, size: 40, @@ -283,29 +303,63 @@ class _MyHomePageState extends State { } if (isGranted == true) { - StoryVm.loadIndexFile('$selectedDirectory/index.ost'); + setState(() { + myPath = selectedDirectory; + }); + bool success = await indexFile.loadIndexFile(selectedDirectory); + if (success) { + showCurrentStory(); + } } } }, color: const Color(0xFFb05728), ), IconButton( - tooltip: 'Search', + tooltip: 'Previous', icon: const Icon(Icons.arrow_circle_left, size: 40), - onPressed: () {}, + onPressed: () { + if (state == PlayerState.inStory) { + StoryVm.previousButton(); + } else if (state == PlayerState.indexFile) { + indexFile.previous(); + showCurrentStory(); + } + }, color: const Color(0xFFb05728), ), IconButton( - tooltip: 'Favorite', + tooltip: 'Next', icon: const Icon(Icons.arrow_circle_right, size: 40), - onPressed: () {}, + onPressed: () { + + if (state == PlayerState.inStory) { + StoryVm.nextButton(); + } else if (state == PlayerState.indexFile) { + indexFile.next(); + showCurrentStory(); + } + }, color: const Color(0xFFb05728), ), ], ), ), floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, + onPressed: () { + + if (state == PlayerState.inStory) { + StoryVm.okButton(); + } else if (state == PlayerState.indexFile) { + String path = indexFile.getCurrentStoryPath(); + + if (path.isNotEmpty) { + StoryVm.start(path); + state = PlayerState.inStory; + } + } + + }, tooltip: 'Ok', backgroundColor: const Color(0xFF0092c8), foregroundColor: Colors.white, diff --git a/story-player/lib/storyvm.dart b/story-player/lib/storyvm.dart deleted file mode 100644 index c6c30a4..0000000 --- a/story-player/lib/storyvm.dart +++ /dev/null @@ -1,204 +0,0 @@ -import 'dart:ffi'; -import 'dart:io'; -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; -import 'package:logger/logger.dart'; - -var logger = Logger(printer: PrettyPrinter(methodCount: 0)); - -Completer periodic(Duration interval, Function(int cycle) callback) { - final done = Completer(); - () async { - var cycle = 0; - while (!done.isCompleted) { - try { - await callback(cycle); - } catch (e, s) { - logger.e("$e", stackTrace: s); - } - cycle++; - await done.future.timeout(interval).onError((error, stackTrace) => false); - } - }(); - return done; -} - -typedef MediaCallbackType = Void Function(Int32 i, Pointer str); -typedef MediaCallback = void Function(Int32 i, Pointer str); - -typedef VmInitializeType = Void Function( - Pointer>); -typedef VmInitialize = void Function( - Pointer>); - -typedef VmStartType = Void Function(Pointer data, Uint32 size); -typedef VmStart = void Function(Pointer data, int size); - -typedef VmRunType = Void Function(); -typedef VmRun = void Function(); - -class StoryVm { - static late DynamicLibrary nativeApiLib; - static late VmInitialize vmInitialize; - static late VmStart vmStart; - static late VmRun vmRun; - - static bool running = false; - - static bool loadLibrary() { - String dllName = 'libstoryvm.so'; - - if (Platform.isMacOS) { - dllName = 'libstoryvm.dylib'; - } else if (Platform.isWindows) { - dllName = 'storyvm.dll'; - } - - final dylib = DynamicLibrary.open(dllName); - - vmInitialize = dylib - .lookup>('storyvm_initialize') - .asFunction(); - - vmStart = - dylib.lookup>('storyvm_start').asFunction(); - - vmRun = - dylib.lookup>('storyvm_start').asFunction(); - - return true; - } - - static void mediaCallback(int i, Pointer str) { - logger.d('Mediatype: $i, Media file: $str'); - } - - static void initialize() { - vmInitialize(Pointer.fromFunction(mediaCallback)); - } - - static Completer task = Completer(); - - static void start() { - task = periodic(const Duration(milliseconds: 10), (cycle) async { - if (running) { - vmRun(); - } - }); - } - - static void stop() { - logger.d('VM stop'); - task.complete(true); - } - - static Uint8List indexFileBuffer = Uint8List(0); - static ByteData streamIndex = ByteData(0); - static int indexFileVersion = 0; - static int storiesCount = 0; - static int readPtr = 0; - static bool indexValid = false; - - static const int tlvArrayType = 0xAB; - static const int tlvObjectType = 0xE7; - static const int tlvIntegerType = 0x77; - static const int tlvStringType = 0x3D; - static const int tlvRealType = 0xB8; - - static List< - ({ - String uuid, - String titleImage, - String titleSound, - String title, - String description, - int version - })> stories = []; - - // Returns the size, 0 if error - static int getTl(int expectedType) { - int size = 0; - if (streamIndex.getUint8(readPtr) == expectedType) { - readPtr++; - size = streamIndex.getUint16(readPtr, Endian.little); - readPtr += 2; - } else { - throw Exception("Expected type: $expectedType"); - } - return size; - } - - static int getIntegerValue() { - int size = getTl(tlvIntegerType); - - if (size == 4) { - int value = streamIndex.getUint32(readPtr, Endian.little); - readPtr += 4; - return value; - } else { - throw Exception("Expected an integer of size 4 bytes"); - } - } - - static String getStringValue() { - int size = getTl(tlvStringType); - if (size > 0) { - String value = - ByteData.sublistView(streamIndex, readPtr, readPtr + size).toString(); - readPtr += size; - return value; - } else { - return ""; - } - } - - static void loadIndexFile(String filePath) async { - final file = File(filePath); - if (!await file.exists()) { - logger.d('Le fichier n\'existe pas.'); - return; - } - - // Ouvrir le fichier en mode lecture binaire - indexFileBuffer = file.readAsBytesSync(); - readPtr = 0; - streamIndex = - ByteData.sublistView(indexFileBuffer, readPtr); // start at zero - - if (indexFileBuffer.lengthInBytes > 3) { - // Root must be an object containing 2 elements - - try { - if (getTl(tlvObjectType) == 2) { - indexFileVersion = getIntegerValue(); - storiesCount = getTl(tlvArrayType); - - for (int i = 0; i < storiesCount; i++) { - if (getTl(tlvObjectType) == 6) { - var record = ( - uuid: getStringValue(), - titleImage: getStringValue(), - titleSound: getStringValue(), - title: getStringValue(), - description: getStringValue(), - version: getIntegerValue() - ); - - logger.d('Found story: $record.title'); - - stories.add(record); - } else { - logger.e("Expected object of 6 elements at root"); - } - } - } else { - logger.e("Expected object of 2 elements at root"); - } - } catch (e) { - logger.e('Exception reading index file: $e'); - } finally {} - } - } -} diff --git a/story-player/pubspec.yaml b/story-player/pubspec.yaml index 14d0700..3a67741 100644 --- a/story-player/pubspec.yaml +++ b/story-player/pubspec.yaml @@ -44,6 +44,9 @@ dependencies: ffi: ^2.1.0 logger: ^2.2.0 file_picker: ^7.0.2 + dqoi: ^1.3.0 + audioplayers: ^5.2.1 + event_bus: ^2.0.0 dev_dependencies: flutter_test: diff --git a/story-player/storyvm/storyvm.cpp b/story-player/storyvm/storyvm.cpp index 5dcf3fc..f957100 100644 --- a/story-player/storyvm/storyvm.cpp +++ b/story-player/storyvm/storyvm.cpp @@ -25,12 +25,12 @@ static media_callback gMediaCallback = nullptr; //--------------------------------------------------------------------------------------- // VM Stuff //--------------------------------------------------------------------------------------- -uint8_t rom_data[16*1024]; -uint8_t ram_data[16*1024]; -chip32_ctx_t chip32_ctx; +static uint8_t rom_data[16*1024]; +static uint8_t ram_data[16*1024]; +static chip32_ctx_t chip32_ctx; -chip32_result_t run_result; +static chip32_result_t run_result; static uint8_t IndexBuf[260]; @@ -67,10 +67,10 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code) if (code == 1) // // Execute media { - printf("SYSCALL 1\n"); - fflush(stdout); -// UnloadTexture(*tex); -// *tex = + std::cout << "[STORYVM] Syscall 1" << std::endl; + // for (int i = 0; i< REGISTER_COUNT; i++) { + // std::cout << "[STORYVM] Reg: " << i << ", value: " << (int)ctx->registers[i] << std::endl; + // } if (ctx->registers[R0] != 0) { @@ -80,18 +80,15 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code) if (gMediaCallback) { + std::cout << "[STORYVM] Execute callback (image)" << std::endl; gMediaCallback(0, image); } - - - // texture = LoadTexture(image_path); // FIXME } else { - // UnloadTexture(texture); // FIXME + std::cout << "[STORYVM] No image" << std::endl; } - if (ctx->registers[R1] != 0) { // sound file name address is in R1 @@ -100,26 +97,20 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code) if (gMediaCallback) { + std::cout << "[STORYVM] Execute callback (sound)" << std::endl; gMediaCallback(1, sound); } - - // gMusic = LoadMusicStream(sound_path); - // gMusic.looping = false; - // gMusicLoaded = true; - - -// FIXME - // if (IsMusicReady(gMusic)) - // { - // PlayMusicStream(gMusic); - // } } + else + { + std::cout << "[STORYVM] No sound" << std::endl; + } + retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause } else if (code == 2) // Wait Event { - printf("SYSCALL 2\n"); - fflush(stdout); + std::cout << "[STORYVM] Syscall 2 (wait for event)" << std::endl; retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause } return retCode; @@ -132,18 +123,24 @@ extern "C" void storyvm_run() if (run_result == VM_OK) { run_result = chip32_step(&chip32_ctx); + + // for (int i = 0; i< REGISTER_COUNT; i++) { + // std::cout << "[STORYVM] Reg: " << i << ", value: " << (int)chip32_ctx.registers[i] << std::endl; + // } } } extern "C" void storyvm_stop() { + std::cout << "[STORYVM] Stop: " << std::endl; run_result = VM_FINISHED; } extern "C" void storyvm_initialize(media_callback cb) { + std::cout << "[STORYVM] Initialize: " << (void *)cb << std::endl; gMediaCallback = cb; chip32_ctx.stack_size = 512; @@ -161,13 +158,41 @@ extern "C" void storyvm_initialize(media_callback cb) run_result = VM_FINISHED; storyvm_stop(); - - std::cout << "[STORYVM] Initialized" << std::endl; } +enum VmEventType {EvNoEvent, EvStep, EvOkButton, EvPreviousButton, EvNextButton, EvAudioFinished, EvStop}; + extern "C" void storyvm_send_event(int event) { + if (event == VmEventType::EvStep) + { + run_result = VM_OK; + } + else if (event == VmEventType::EvOkButton) + { + chip32_ctx.registers[R0] = 0x01; + run_result = VM_OK; + } + else if (event == VmEventType::EvPreviousButton) + { + chip32_ctx.registers[R0] = 0x02; + run_result = VM_OK; + } + else if (event == VmEventType::EvNextButton) + { + chip32_ctx.registers[R0] = 0x04; + run_result = VM_OK; + } + else if (event == VmEventType::EvAudioFinished) + { + chip32_ctx.registers[R0] = 0x08; + run_result = VM_OK; + } + else if (event == VmEventType::EvStop) + { + run_result = VM_FINISHED; + } } extern "C" void storyvm_start(const uint8_t *data, uint32_t size) @@ -177,12 +202,12 @@ extern "C" void storyvm_start(const uint8_t *data, uint32_t size) memcpy(chip32_ctx.rom.mem, data, size); run_result = VM_OK; chip32_initialize(&chip32_ctx); + std::cout << "[STORYVM] Start" << std::endl; } else { run_result = VM_FINISHED; + std::cout << "[STORYVM] Not started (not enough memory)" << std::endl; } - - std::cout << "[STORYVM] Start" << std::endl; }