diff --git a/art/pack/intro_drako_le_dragon.mp3 b/art/pack/intro_drako_le_dragon.mp3 new file mode 100644 index 0000000..ba73362 Binary files /dev/null and b/art/pack/intro_drako_le_dragon.mp3 differ diff --git a/art/pack/la_fee_luminelle.mp3 b/art/pack/la_fee_luminelle.mp3 new file mode 100644 index 0000000..54867a1 Binary files /dev/null and b/art/pack/la_fee_luminelle.mp3 differ diff --git a/art/pack/le_sceptre.mp3 b/art/pack/le_sceptre.mp3 new file mode 100644 index 0000000..cfdb806 Binary files /dev/null and b/art/pack/le_sceptre.mp3 differ diff --git a/art/pack/qui_drako_va_t_il_rencontrer.mp3 b/art/pack/qui_drako_va_t_il_rencontrer.mp3 new file mode 100644 index 0000000..f6f8e2d Binary files /dev/null and b/art/pack/qui_drako_va_t_il_rencontrer.mp3 differ diff --git a/art/pack/qui_sera_le_hero.m4a b/art/pack/qui_sera_le_hero.m4a deleted file mode 100644 index 1bde83f..0000000 Binary files a/art/pack/qui_sera_le_hero.m4a and /dev/null differ diff --git a/art/pack/qui_sera_le_hero.mp3 b/art/pack/qui_sera_le_hero.mp3 new file mode 100644 index 0000000..9f0cf93 Binary files /dev/null and b/art/pack/qui_sera_le_hero.mp3 differ diff --git a/art/pack/story1_drako_luminelle_sceptre.mp3 b/art/pack/story1_drako_luminelle_sceptre.mp3 new file mode 100644 index 0000000..bf19ba5 Binary files /dev/null and b/art/pack/story1_drako_luminelle_sceptre.mp3 differ diff --git a/art/pack/un_oiseau.mp3 b/art/pack/un_oiseau.mp3 new file mode 100644 index 0000000..0b3d032 Binary files /dev/null and b/art/pack/un_oiseau.mp3 differ diff --git a/software/chip32/chip32_assembler.cpp b/software/chip32/chip32_assembler.cpp index 0487c2a..3ef4a75 100644 --- a/software/chip32/chip32_assembler.cpp +++ b/software/chip32/chip32_assembler.cpp @@ -340,6 +340,7 @@ bool Assembler::Parse(const std::string &data) if (pos != std::string::npos) { line.erase(pos); } + line.erase(std::remove(line.begin(), line.end(), '\t'), line.end()); // remove tabulations if (std::all_of(line.begin(), line.end(), ::isspace)) continue; // Split the line diff --git a/software/chip32/chip32_vm.h b/software/chip32/chip32_vm.h index dd5ec8d..98c8dd0 100644 --- a/software/chip32/chip32_vm.h +++ b/software/chip32/chip32_vm.h @@ -40,44 +40,44 @@ extern "C" { typedef enum { // system: - OP_NOP = 0, // do nothing - OP_HALT = 1, // halt execution - OP_SYSCALL = 2, // system call handled by user-registered function, 4 arguments (R0 - R3) passed by value + OP_NOP = 0, ///< do nothing + OP_HALT = 1, ///< halt execution + OP_SYSCALL = 2, ///< system call handled by user-registered function, 4 arguments (R0 - R3) passed by value // constants: - OP_LCONS = 3, // store a value in a register, e.g.: lcons r0, 0xA2 0x00 0x00 0x00 - // can also load a variable address: lcons r2, $DataInRam + OP_LCONS = 3, ///< store a value in a register, e.g.: lcons r0, 0xA2 0x00 0x00 0x00 + ///< can also load a variable address: lcons r2, $DataInRam // register operations: - OP_MOV = 4, // copy a value between registers, e.g.: mov r0, r2 + OP_MOV = 4, ///< copy a value between registers, e.g.: mov r0, r2 // stack: - OP_PUSH = 5, // push a register onto the stack, e.g.: push r0 - OP_POP = 6, // pop the first element of the stack to a register, e.g.: pop r0 + OP_PUSH = 5, ///< push a register onto the stack, e.g.: push r0 + OP_POP = 6, ///< pop the first element of the stack to a register, e.g.: pop r0 // memory get/set from/to an address. The last argument is a size (1, 2 or 4 bytes) - OP_STORE = 7, // copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes) - OP_LOAD = 8, // copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte) + OP_STORE = 7, ///< copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes) + OP_LOAD = 8, ///< copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte) // arithmetic: - OP_ADD = 9, // sum and store in first reg, e.g.: add r0, r2 - OP_SUB = 10, // subtract and store in first reg, e.g.: sub r0, r2 - OP_MUL = 11, // multiply and store in first reg, e.g.: mul r0, r2 - OP_DIV = 12, // divide and store in first reg, remain in second, e.g.: div r0, r2 + OP_ADD = 9, ///< sum and store in first reg, e.g.: add r0, r2 + OP_SUB = 10, ///< subtract and store in first reg, e.g.: sub r0, r2 + OP_MUL = 11, ///< multiply and store in first reg, e.g.: mul r0, r2 + OP_DIV = 12, ///< divide and store in first reg, remain in second, e.g.: div r0, r2 - OP_SHL = 13, // logical shift left, e.g.: shl r0, r1 - OP_SHR = 14, // logical shift right, e.g.: shr r0, r1 - OP_ISHR = 15, // arithmetic shift right (for signed values), e.g.: ishr r0, r1 + OP_SHL = 13, ///< logical shift left, e.g.: shl r0, r1 + OP_SHR = 14, ///< logical shift right, e.g.: shr r0, r1 + OP_ISHR = 15, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1 - OP_AND = 16, // and two registers and store result in the first one, e.g.: and r0, r1 - OP_OR = 17, // or two registers and store result in the first one, e.g.: or r0, r1 - OP_XOR = 18, // xor two registers and store result in the first one, e.g.: xor r0, r1 - OP_NOT = 19, // not a register and store result, e.g.: not r0 + OP_AND = 16, ///< and two registers and store result in the first one, e.g.: and r0, r1 + OP_OR = 17, ///< or two registers and store result in the first one, e.g.: or r0, r1 + OP_XOR = 18, ///< xor two registers and store result in the first one, e.g.: xor r0, r1 + OP_NOT = 19, ///< not a register and store result, e.g.: not r0 // branching/functions - OP_CALL = 20, // set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00 - OP_RET = 21, // return to the address of last callee (RA), e.g.: ret - OP_JUMP = 22, // jump to address (can use label or address), e.g.: jump .my_label - OP_SKIPZ = 23, // skip next instruction if zero, e.g.: skipz r0 - OP_SKIPNZ = 24, // skip next instruction if not zero, e.g.: skipnz r2 + OP_CALL = 20, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00 + OP_RET = 21, ///< return to the address of last callee (RA), e.g.: ret + OP_JUMP = 22, ///< jump to address (can use label or address), e.g.: jump .my_label + OP_SKIPZ = 23, ///< skip next instruction if zero, e.g.: skipz r0 + OP_SKIPNZ = 24, ///< skip next instruction if not zero, e.g.: skipnz r2 INSTRUCTION_COUNT } chip32_instruction_t; @@ -186,6 +186,10 @@ typedef struct typedef uint8_t (*syscall_t)(uint8_t); +#define SYSCALL_RET_OK 0 ///< Default state, continue execution immediately +#define SYSCALL_RET_WAIT_EV 1 ///< Sets the VM in wait for event state + + typedef struct { virtual_mem_t rom; diff --git a/story-editor/nodeeditor b/story-editor/nodeeditor index 3b9ae07..ba75bd9 160000 --- a/story-editor/nodeeditor +++ b/story-editor/nodeeditor @@ -1 +1 @@ -Subproject commit 3b9ae0777cd52d6037bdcc6e0b05b57c5eb8701e +Subproject commit ba75bd93e1b1c5d5bc387e49f35737697d180b30 diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 6ebcd56..8012aac 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -82,7 +82,7 @@ MainWindow::MainWindow() addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock); m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction(), m_ostHmiDock); - connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() { + connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [&]() { QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton)); }); @@ -98,16 +98,16 @@ MainWindow::MainWindow() addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock); m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction(), m_vmDock); - connect(m_vmDock, &VmDock::sigCompile, [=]() { + connect(m_vmDock, &VmDock::sigCompile, [&]() { // m_scriptEditorDock->setScript(m_project.BuildResources()); }); - connect(m_vmDock, &VmDock::sigStepInstruction, [=]() { + connect(m_vmDock, &VmDock::sigStepInstruction, [&]() { QCoreApplication::postEvent(this, new VmEvent(VmEvent::evStep)); }); - connect(m_vmDock, &VmDock::sigBuild, [=]() { - buildScript(); + connect(m_vmDock, &VmDock::sigBuild, [&]() { + BuildScript(); }); m_ramView = new MemoryViewDock("RamViewDock", "RAM"); @@ -176,7 +176,7 @@ MainWindow::MainWindow() NewProject(); }); - connect(m_toolbar, &ToolBar::sigSave, this, [=]() { + connect(m_toolbar, &ToolBar::sigSave, this, [&]() { SaveProject(); }); @@ -215,6 +215,39 @@ MainWindow::MainWindow() CloseProject(); RefreshProjectInformation(); + // Prepare run timer + m_runTimer = new QTimer(this); + m_runTimer->setSingleShot(true); + connect(m_runTimer, &QTimer::timeout, this, [&]() { + if (m_dbg.run_result == VM_OK) + { + stepInstruction(); + } + + if (m_dbg.run_result == VM_OK) + { + // Restart timer + m_runTimer->start(100); + } + else if (m_dbg.run_result == VM_FINISHED) + { + m_dbg.running = false; + } + else if (m_dbg.run_result == VM_WAIT_EVENT) + { + m_runTimer->stop(); // just to make sure + } + }); + + connect(&m_model, &StoryGraphModel::sigAudioStopped, this, [&]() { + + if ((m_dbg.run_result == VM_WAIT_EVENT) && (m_dbg.running)) + { + // Continue execution of node + m_runTimer->start(100); + } + }); + // QMetaObject::invokeMethod(this, "slotWelcome", Qt::QueuedConnection); } @@ -224,8 +257,7 @@ void MainWindow::BuildAndRun() // FIXME // 2. Generate the assembly code from the model - std::string code = m_project.BuildResources() + "\n"; - code += m_model.Build(); + std::string code = m_model.Build(); // Add global functions code += ReadResourceFile(":/scripts/media.asm").toStdString(); @@ -235,8 +267,14 @@ void MainWindow::BuildAndRun() m_scriptEditorDock->setScript(code.c_str()); // 3. Compile the assembly to machine binary - // buildScript(); + BuildScript(); + // 4. Run the VM code! + if (m_dbg.run_result == VM_OK) + { + m_dbg.running = true; + m_runTimer->start(100); + } } @@ -406,7 +444,7 @@ void MainWindow::updateAll() m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size); } -QString MainWindow::GetFileName(uint32_t addr) +QString MainWindow::GetFileNameFromMemory(uint32_t addr) { char strBuf[100]; bool isRam = addr & 0x80000000; @@ -452,19 +490,21 @@ bool MainWindow::event(QEvent *event) uint8_t MainWindow::Syscall(uint8_t code) { - uint8_t retCode = 0; + uint8_t retCode = SYSCALL_RET_OK; qDebug() << "SYSCALL: " << (int)code; // Media if (code == 1) { // image file name address is in R0 - QString imageFile = GetFileName(m_chip32_ctx.registers[R0]); + QString imageFile = m_model.BuildFullImagePath(GetFileNameFromMemory(m_chip32_ctx.registers[R0])); // sound file name address is in R1 - QString soundFile = GetFileName(m_chip32_ctx.registers[R1]); + QString soundFile = m_model.BuildFullSoundPath(GetFileNameFromMemory(m_chip32_ctx.registers[R1])); qDebug() << "Image: " << imageFile << ", Sound: " << soundFile; m_ostHmiDock->SetImage(imageFile); + m_model.PlaySound(soundFile); + retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause } // WAIT EVENT bits: // 0: block @@ -478,7 +518,7 @@ uint8_t MainWindow::Syscall(uint8_t code) // Event mask is located in R0 // optional timeout is located in R1 // if timeout is set to zero, wait for infinite and beyond - retCode = 1; // set the VM in pause + retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause } @@ -491,8 +531,11 @@ void MainWindow::stepInstruction() updateAll(); } -void MainWindow::buildScript() +void MainWindow::BuildScript() { + m_dbg.run_result = VM_FINISHED; + m_dbg.running = false; + if (m_assembler.Parse(m_scriptEditorDock->getScript().toStdString()) == true ) { if (m_assembler.BuildBinary(m_program, m_result) == true) diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index ee23027..8fb09f0 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -136,6 +137,7 @@ private: GraphicsView *m_view{nullptr}; // Qt stuff + QTimer *m_runTimer{nullptr}; ToolBar *m_toolbar{nullptr}; OstHmiDock *m_ostHmiDock{nullptr}; ResourcesDock *m_resourcesDock{nullptr}; @@ -166,12 +168,12 @@ private: void createStatusBar(); void SaveProject(); void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId); - void buildScript(); + void BuildScript(); void highlightNextLine(); void readSettings(); void updateAll(); uint8_t Syscall(uint8_t code); - QString GetFileName(uint32_t addr); + QString GetFileNameFromMemory(uint32_t addr); bool event(QEvent *event); void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); diff --git a/story-editor/src/media-node.ui b/story-editor/src/media-node.ui index f7a52d5..6f2c2f8 100644 --- a/story-editor/src/media-node.ui +++ b/story-editor/src/media-node.ui @@ -148,6 +148,9 @@ + 0 + + 1 diff --git a/story-editor/src/media_node_model.cpp b/story-editor/src/media_node_model.cpp index 1646ce3..897dd96 100644 --- a/story-editor/src/media_node_model.cpp +++ b/story-editor/src/media_node_model.cpp @@ -129,10 +129,10 @@ std::string MediaNodeModel::GenerateConstants() } if (sound.size() > 0) { - s = StoryProject::FileToConstant(sound); + s += StoryProject::FileToConstant(sound); } - // Generate choice table if needed (out ports > 1) + // FIXME: Generate choice table if needed (out ports > 1) return s; } @@ -142,8 +142,8 @@ std::string MediaNodeModel::Build() std::stringstream ss; ss << R"(; ---------------- )" << GetNodeTitle() << "\n"; - std::string image = m_mediaData["image"].get(); - std::string sound = m_mediaData["sound"].get(); + std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get()); + std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get()); if (image.size() > 0) { ss << "lcons r0, $" << image << "\n"; @@ -161,6 +161,38 @@ std::string MediaNodeModel::Build() { ss << "lcons r1, 0\n"; } + // Call the media executor (image, sound) + ss << "syscall 1\n"; + + + std::unordered_set conns = m_model.allConnectionIds(getNodeId()); + + int nb_out_ports = 0; + + for (auto & c : conns) + { + if (c.outNodeId > 0) + { + nb_out_ports++; + } + } + + if (nb_out_ports == 0) + { + ss << "halt\n"; + } + else + { + + } + + // Check output connections number + // == 0: end, generate halt + // == 1: jump directly to the other node + // > 1 : call the node choice manager + +// lcons r0, $ChoiceObject +// jump .media ; no return possible, so a jump is enough /* diff --git a/story-editor/src/ost-hmi.ui b/story-editor/src/ost-hmi.ui index de636a1..089d5e1 100644 --- a/story-editor/src/ost-hmi.ui +++ b/story-editor/src/ost-hmi.ui @@ -26,7 +26,13 @@ 320 - 200 + 240 + + + + + 320 + 240 @@ -35,6 +41,9 @@ + + true + diff --git a/story-editor/src/story_graph_model.cpp b/story-editor/src/story_graph_model.cpp index 08373a6..95ffc2c 100644 --- a/story-editor/src/story_graph_model.cpp +++ b/story-editor/src/story_graph_model.cpp @@ -12,6 +12,13 @@ StoryGraphModel::StoryGraphModel(StoryProject &project) m_player = new QMediaPlayer; m_audioOutput = new QAudioOutput; m_player->setAudioOutput(m_audioOutput); + + connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) { + if (newState == QMediaPlayer::PlaybackState::StoppedState) { + m_player->stop(); + emit sigAudioStopped(); + } + }); } StoryGraphModel::~StoryGraphModel() @@ -173,6 +180,9 @@ QVariant StoryGraphModel::nodeData(NodeId nodeId, NodeRole role) const result = QVariant::fromValue(w); break; } + case NodeRole::Id: + result = model->getNodeId(); + break; } return result; diff --git a/story-editor/src/story_graph_model.h b/story-editor/src/story_graph_model.h index 8630db4..b2a3e16 100644 --- a/story-editor/src/story_graph_model.h +++ b/story-editor/src/story_graph_model.h @@ -145,6 +145,7 @@ public: NodeId FindFirstNode() const; signals: void sigChooseFile(NodeId id, const QString &type); + void sigAudioStopped(); private: StoryProject &m_project; diff --git a/story-editor/src/story_project.cpp b/story-editor/src/story_project.cpp index fb34103..0367ddc 100644 --- a/story-editor/src/story_project.cpp +++ b/story-editor/src/story_project.cpp @@ -247,13 +247,18 @@ void StoryProject::ReplaceCharacter(std::string &theString, const std::string &t while (found != std::string::npos); } +std::string StoryProject::RemoveFileExtension(const std::string &FileName) +{ + std::string f = GetFileName(FileName); + std::string ext = GetFileExtension(f); + EraseString(f, "." + ext); + return f; +} + std::string StoryProject::FileToConstant(const std::string &FileName) { - std::string fileName = GetFileName(FileName); - std::string ext = GetFileExtension(fileName); - EraseString(fileName, "." + ext); // on retire l'extension du pack - - return "$" + fileName + " DC8 \"" + fileName + "." + ext + "\", 8\r\n"; + std::string f = RemoveFileExtension(FileName); + return "$" + f + " DC8 \"" + FileName + "\", 8\r\n"; } void StoryProject::AppendResource(const Resource &res) @@ -292,24 +297,6 @@ std::string StoryProject::GetFileExtension(const std::string &fileName) return ""; } -std::string StoryProject::BuildResources() -{ - std::stringstream chip32; - - chip32 << "\tjump .entry\r\n"; - - for (auto & r : m_resources) - { - std::string fileName = GetFileName(r.file); - std::string ext = GetFileExtension(fileName); - EraseString(fileName, "." + ext); // on retire l'extension du pack - - chip32 << "$" << fileName << " DC8 \"" << fileName + "." + ext << "\", 8\r\n"; - } - chip32 << ".entry:\r\n"; - - return chip32.str(); -} void StoryProject::SetImageFormat(ImageFormat format) { diff --git a/story-editor/src/story_project.h b/story-editor/src/story_project.h index 5202516..ee1cf87 100644 --- a/story-editor/src/story_project.h +++ b/story-editor/src/story_project.h @@ -68,7 +68,6 @@ struct StoryProject m_initialized = false; } - std::string BuildResources(); void SetImageFormat(ImageFormat format); void SetSoundFormat(SoundFormat format); void SetDisplayFormat(int w, int h); @@ -85,6 +84,7 @@ struct StoryProject static std::string GetFileExtension(const std::string &FileName); static std::string GetFileName(const std::string &path); + static std::string RemoveFileExtension(const std::string &FileName); static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace); static std::string FileToConstant(const std::string &FileName);