Tiger 2-XL 2.0

Marc Simon Frei, Lucerne

 

Tiger 2-XL 2.0: Rebirth of a Legend

 We all remember the magical feeling when the Tiger 2-XL asked us a question for the first time. We pressed a button, and he seemed to truly listen to us. But the magic was an illusion, woven from four parallel magnetic tracks on an analog cassette tape. A brilliant trick, but one that came at the cost of tangled tapes, pitch wobbles, and limited capacity. This project breaks the seals of that analog chain. We have preserved the clunky, gray casing, but its interior has undergone a radical metamorphosis. Where mechanics and magnetic tape once clattered, a modern digital heart now pulsates, powered by CircuitPython. Cassettes are history. An entire cosmos of albums and stories now finds its place on a tiny SD card. The legendary four buttons no longer control the mechanical angle of a tape head. Instead, they have been digitally refined; backed by a custom software debounce algorithm, they trigger lightning-fast, flawless interrupts in a modern microcontroller (Adafruit Grand Central M4 Express). The interaction is no longer rigid, it is intelligent. The robot now truly remembers where it is in the story.

 

His mouth has learned to speak

Instead of simple little lamps, a high-resolution LED matrix (Adafruit IS31FL3741) shapes his reactions into pulsating light. The true magic lies in its organic connection to the audio: the microcontroller measures the analog audio signal's peak-to-peak values in real-time, calculating fluid lip-sync animations that dance perfectly to the rhythm of his unmistakable voice. And because a truly intelligent companion must not only know how to speak but also how to be silent, the audio system was meticulously upgraded. Vintage speakers combined with modern electronics often suffer from a subtle background hiss. To completely eliminate this, the code sends the I2C audio amplifier into a physical shutdown mode the moment the volume drops to zero. When 2-XL is silent, he is absolutely silent.

 

From Code to Audio Drama: The Magic of the 4 Tracks

An intelligent robot needs intelligent stories. To fill the 2-XL with new adventures, I developed the “Mega-Sync Generator,” a powerful script that acts as director, sound engineer, and voice actor all in one. Instead of tediously shifting audio tracks back and forth in editing software, the system uses artificial intelligence (OpenAI) to automatically transform written scripts into cinematic audio dramas. The script recognizes different speaking roles and dynamically assigns different voices to them. The true technical feat, however, lies in the synchronization. The 2-XL reads four audio tracks simultaneously. When you press a button, you don't jump to a different point in time on the track; instead, you simply switch the audible track (similar to changing the audio language on a DVD). This means: All decisions must ultimately converge again, accurate down to the exact millisecond. If Answer A takes ten seconds and Answer B takes fifteen seconds, our generator automatically calculates the difference. It fills the shorter track with exactly five seconds of silence ("padding"). By integrating FFmpeg, four parallel, mathematically perfect synchronized audio tracks are compiled. No matter what the player decides—the story never misses a beat.

 

A Bridge Between Generations

I rebuilt this for my son, so he can experience the exact same magic that fascinated me as a child. I didn't want to simply hand him an old toy from the past, but rather bring the 2-XL into the era he is growing up in today. It is a bridge between generations: the nostalgic shell of my childhood, filled with the endless imagination and technology of his future. Now he can experience his very own adventures with the 2-XL, just like I did back then, only completely without limits.

 


 

Hardware

  • Brain: Adafruit Grand Central M4 Express

  • Mouth: Adafruit IS31FL3741 (13x9 RGB LED Matrix) via I2C

  • Voice: Adafruit TPA2016 I2C Audio Amplifier (with physical shutdown for zero hiss)

  • Vocal Cords: Original mono speaker (connected to the left output channel of the amplifier)
  • Memory: Standard Micro-SD Card Module (SPI)

  • Interface: Original 2-XL buttons (software debounced) and potentiometer (Analog In)

  • Navigation: Additional physical push-button for "Next Folder" skipping (Digital In)

 


 

2-XL 4-Track Simulator

The tape head reads all tracks in real-time.

 
 
 
 
 
2-XL ROBOT A
 
 
 
 
PROGRAM: MULTI-TRACK INTERACTIVE
 
 
 
 
 
 
 
 

ACTIVE TRACK: TRACK 1

"Because the mechanical tape simply kept running continuously, the 2-XL had absolutely no loading times, no clicking, and no mechanical seeking. When the robot asked a question, four different answers played simultaneously on all four tracks. If you pressed 'B', the robot switched to track 2 at lightning speed..."

 


 

 


 

2-XL Simulator (ENG)

2-XL Simulator (GER)

2-XL Simulator (ITA)

 

Here's an example of how to generate your own story with OpenAI:

# ==============================================================================
# 2-XL MEGA-SYNC GENERATOR: AUDIOBOOK "ALEXANDER THE MAGISTER" (PHILOSOPHY)
# ==============================================================================

# ⚠️ ENTER YOUR OPENAI API KEY HERE:
$OpenAI_API_Key = "ENTER_YOUR_OPENAI_API_KEY_HERE"

# Set console output to UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$desktopPath = [Environment]::GetFolderPath("Desktop")
$outDir = "$desktopPath2-XL_Alexander_TheMagister"
if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null }

Write-Host "⚖️ Opening the Book of Laws... Loading the philosophical epic..." -ForegroundColor Cyan

# ==============================================================================
# FFmpeg: GENERATE SILENCE FILES FOR THE PAUSES (WITH -nostdin)
# ==============================================================================
$silence3s = "$outDirsilence_3s.wav"
$silence2s = "$outDirsilence_2s.wav"

if (-not (Test-Path $silence3s)) {
    Write-Host "Generating 3-second action pause..." -ForegroundColor DarkGray
    & ffmpeg -nostdin -y -v error -f lavfi -i anullsrc=r=22050:cl=mono -t 3 -c:a pcm_s16le $silence3s
}
if (-not (Test-Path $silence2s)) {
    Write-Host "Generating 2-second breath pause..." -ForegroundColor DarkGray
    & ffmpeg -nostdin -y -v error -f lavfi -i anullsrc=r=22050:cl=mono -t 2 -c:a pcm_s16le $silence2s
}

# ==============================================================================
# THE SCRIPT: ALEXANDER THE MAGISTER (8 PHILOSOPHICAL CHAPTERS)
# ==============================================================================

$Story = @(
    @{
        Type = "Intro"
        Text = "[onyx] Hear the whispers of truth, traveler. We are in the mythical realm of Aethelgard. A world where the sword was the law of the past, but now the epoch of the mind dawns. [alloy] My name is Alexander. I am the youngest Magister of Justice the realm has ever seen. I travel from city to city to judge those conflicts that break ordinary judges. [onyx] I am 2-X L, the speaking codex book in Alexander's hands. I know every paragraph, but true wisdom is not found in any book. Traveler, today you are Alexander's inner compass. You will be confronted with paradoxes, Solomon's riddles, and the essence of morality. The judgment lies in your hands. ... The courtroom opens... now!"
    },
    @{
        Type     = "Question"
        Title    = "1_TheChalkCircle"
        Question = "Chapter 1: The Two Mothers. ... Alexander sits on the wooden judgment seat of the village. Before him cries a little boy. Two women tug at his arms. [nova] Magister! I gave birth to this child! I am his biological mother, he is my flesh and blood! [shimmer] Liar! You abandoned him in the war to save your wealth! I raised him for years, nourished and protected him! He is my son in my heart! [alloy] One demands her biological right, the other her moral right. I have a circle of chalk drawn on the floor and place the child in the middle. Both women are to grab one of the child's arms. Whoever pulls the child to herself out of the circle, gets him. [onyx] The women take hold. Officer of Justice, what is the true Solomonic judgment when they begin to pull? ... Press Button 2: The biological mother pulls harder and wins. The law of blood is absolute and untouchable. ... Press Button 3: The foster mother wins, because Alexander secretly breaks the law and grants her the victory, as she loves the child more. ... Or press Button 4: Wait for the moment the true mother lets go to avoid tearing the child apart, and grant the child to her. ... Pass your judgment. Press the button ... now!"
        Ans_2    = "[alloy] Button 2. The law of blood. The biological mother tugs with all her might and pulls the crying boy out of the circle. The foster mother collapses sobbing. [onyx] Alexander, you judged according to the strictest interpretation of biology. The child belongs to the woman who bore him. But you see no love in the child's eyes, only pain. You upheld the law, but betrayed justice."
        Ans_3    = "[alloy] Button 3. The right of the heart! I order the guards to help the foster mother. She gets the child. [onyx] The biological mother screams bloody murder. She curses the judge who breaks the written law. Alexander, you acted out of compassion, but destroyed the trust in the impartiality of your office. When a judge decides by sympathy instead of rules, arbitrariness begins."
        Ans_4    = "[alloy] Button 4. Pull! [onyx] The women tug. The child cries out in pain. Suddenly the foster mother lets go weeping, so as not to dislocate the boy's arms. The biological mother triumphs. [alloy] Halt! The woman who would rather give up her claim than see the child bleed is the true mother! Give her the boy! [onyx] A brilliant psychological maneuver. You did not seek the truth in blood, but in sacrifice."
        Reaction = "[alloy] The courtroom erupts in cheers. The true mother embraces the child. [onyx] A classic Caucasian chalk circle dilemma, sovereignly solved. You understood that motherhood is not purely a biological fact, but an ethical act. Alexander closes his book. The journey continues, to more complex questions of human nature."
    },
    @{
        Type     = "Question"
        Title    = "2_TheShipOfTheseus"
        Question = "Chapter 2: The Iron Veteran. ... Alexander reaches the capital. Before him stands an ancient war veteran. He demands the lifelong royal pension promised to him forty years ago for saving the city. [fable] I am General Kael! Give me what is rightfully mine! [nova] Magister, this man is a fraud! [onyx] The royal treasurer steps forward. [nova] He has suffered so many injuries in all the wars that mages and blacksmiths have replaced every part of his body over the decades. His arms are made of steel, his legs of bronze, even his heart is a magical crystal! Not a single piece of flesh of General Kael remains. This golem has no claim! [alloy] The old paradox of the Ship of Theseus. If you replace every plank of a ship, is it still the same ship? Is this machine-man still General Kael? ... Press Button 2: Judge in favor of the treasurer. The body is gone, so the man is dead. No pension. ... Press Button 3: Judge in favor of the General. Identity lies in memories and the soul, not in matter. ... Or press Button 4: Split the pension. Recognize him as a new being who inherits from Kael, but is not Kael himself. ... What constitutes identity? Press the button ... now!"
        Ans_2    = "[alloy] Button 2. Matter defines being. I speak: You are not General Kael, you are merely his mechanical replica! [onyx] The veteran sadly lowers his steel head. The treasurer rejoices. Alexander, you applied an extremely materialistic philosophy. You saved the realm from costs, but robbed a hero of his dignity, one who literally sacrificed everything for his country."
        Ans_3    = "[alloy] Button 3. Identity is immaterial! [onyx] Alexander rises. [alloy] The courage that saved this city did not reside in muscles, but in his consciousness! He is General Kael! Pay him his pension! [onyx] The iron veteran salutes with tears in his eyes. You chose a deeply humanistic definition of life. The sum of our memories is more powerful than the cells of our body."
        Ans_4    = "[alloy] Button 4. A compromise. The past is dead, the future is new. [onyx] Alexander declares that the old Kael died in the war, and this golem is his rightful heir. He receives half of the pension. [alloy] A wise, but unsatisfying judgment. [onyx] Neither the treasurer nor the General are happy. In philosophy, the attempt to please everyone often leads to the dilution of truth."
        Reaction = "[alloy] The veteran leaves the hall. His steel steps echo heavily on the marble. [onyx] Consciousness is the greatest riddle of the cosmos. We completely change our skin, our cells, our opinions over the course of a lifetime. Yet something within us remains constant. Alexander notes the judgment. The next city waits."
    },
    @{
        Type     = "Question"
        Title    = "3_TheForeseer"
        Question = "Chapter 3: The Crime of Tomorrow. ... Alexander travels to the mountain village of Delphi. A famous seer, whose prophecies have always been one hundred percent fulfilled over the last twenty years, steps forward with a terrible accusation. [echo] Magister! The blacksmith Marcus will strike the baker dead in an argument tomorrow at noon! I have seen it absolutely clearly in my visions! [onyx] The villagers have already put Marcus in chains. Marcus weeps and protests his innocence. [fable] I am a peaceful man! I have never harmed a hair on the baker's head! You cannot punish me for something I haven't done! [alloy] The village demands that Marcus be executed or locked away for life to prevent the murder. If the seer never errs, the murder is as good as done. Is there free will, or is the future predetermined? ... Press Button 2: Lock Marcus up immediately. A guaranteed crime must be stopped preventively to save a life. ... Press Button 3: Let Marcus go free. No one may be punished for a deed that does not yet exist in reality. Free will prevails. ... Or press Button 4: Lock Marcus and the baker together in a guarded cell for the next two days to artificially alter the circumstances of the prophecy. ... Judge the future. Press the button ... now!"
        Ans_2    = "[alloy] Button 2. Safety first! Lock Marcus up! [onyx] Marcus is thrown into a dark dungeon. The baker survives the next day. The village celebrates you. [alloy] I have prevented a murder! [onyx] But at what cost? You have imprisoned an innocent man. You destroyed the principle of the presumption of innocence and declared the free will of man to be nonexistent. A dystopian form of justice."
        Ans_3    = "[alloy] Button 3. I cannot punish thoughts and I cannot punish the future. Let him go free! [onyx] Marcus is freed under the protest of the village. The next day he gets into a trivial argument about flour with the baker. The prophecy fulfills itself: Marcus strikes out in the heat of the moment, the baker dies. [alloy] Oh gods... I allowed this to happen! [onyx] You believed in free will, but fate was stronger. A terrible triumph of ethical adherence to principles over human life."
        Ans_4    = "[alloy] Button 4. We break the prophecy through logic! Lock them both up together! [onyx] Marcus and the baker spend the fateful day together in a cell under the strictest guard. They talk, they argue, but there are no weapons and no escalation. Noon passes. No one dies. [alloy] The future is not carved in stone! [onyx] A brilliant way out. You have proven that foreknowledge of an event gives us the power to prevent exactly that event through free will."
        Reaction = "[alloy] The seer stands there stunned. His perfect chain of prophecies is broken. [onyx] The future is an ocean of possibilities, Alexander. Whoever believes fate is absolute denies humanity its greatest gift: the ability to change. A profound day for Aethelgard. We move on."
    },
    @{
        Type     = "Question"
        Title    = "4_TheStolenBread"
        Question = "Chapter 4: The Cold Scales. ... A bitterly cold winter's day in an impoverished city. An emaciated man in rags kneels before Alexander. He has stolen three loaves of bread from the bakery. [shimmer] Magister, my three children are dying! I had no gold, but they haven't eaten in days! I beg you! [onyx] But the baker who accuses him is also desperate. [nova] Alexander, I am almost bankrupt myself! This theft ruins me! If you let thieves get away unpunished, tomorrow my whole bakery will be plundered, and then my family will starve! The law demands the chopping off of two fingers or ten years in the dungeon! [alloy] A classic balancing of interests. On one side, the bare survival of a family, on the other, the right to property and the stability of society. Mercy against order. What should I do? ... Press Button 2: Apply the law strictly. Dungeon for the father. Order must prevail. ... Press Button 3: Let the father go completely unpunished out of pure compassion. His motive was noble. ... Or press Button 4: Sentence the father to a penalty, but pay for the bread out of your own pocket and let him work off the penalty as an assistant at the baker's. ... How blind is Lady Justice? Press the button ... now!"
        Ans_2    = "[alloy] Button 2. The law is an iron pillar. Ten years in the dungeon! [onyx] The father collapses weeping and is led away. The baker is satisfied, but darkness spreads through the village. [alloy] The man's children... what will become of them? [onyx] They will succumb to the winter. You maintained legal order perfectly, but sacrificed humanity. A judgment like a blade of ice."
        Ans_3    = "[alloy] Button 3. I cannot punish a father for his love. He is free! Go in peace! [onyx] The father kisses the hem of your robe and rushes to his children. But the next day, the bakery is completely plundered by other hungry people citing your lenient judgment. The baker loses everything. [alloy] I wanted to do good, and I sowed chaos. [onyx] Whoever bends the law out of pity invites injustice to spread on the other side."
        Ans_4    = "[alloy] Button 4. A crime occurred. The debt is three silver coins. [onyx] Alexander pulls out his own purse and pays the baker. [alloy] The theft is settled. But for breaking the peace, the man will fire the ovens in your bakery for free for four weeks. [onyx] The baker receives help, the father can feed his children and learns a trade. You practiced restorative justice – the damage was healed, not repaid with pain."
        Reaction = "[alloy] The scent of fresh bread wafts through the alleys while the father stokes the ovens. [onyx] Justice is not a sword, Alexander, it is a toolbox. Punishment alone heals no social wounds. True wisdom finds a way to reform the perpetrator and make the victim whole. You are growing into your office."
    },
    @{
        Type     = "Question"
        Title    = "5_TheBurningHouse"
        Question = "Chapter 5: The Value of Life. ... A terrible catastrophe. A fire has broken out in a large building. It's ablaze. Alexander stands before it with the guards. [onyx] Magister, we can only send one rescue squad before the roof collapses! [alloy] Who is still inside? [onyx] In the left wing, five old scholars are trapped. They carry the entire historical and medical knowledge of the realm in their heads. In the right wing, a single seven-year-old child of a maid is crying. The flames separate the paths. The squad can only reach one wing. [alloy] The brutal arithmetic of life. Do I save five brilliant old men whose knowledge could heal thousands in the future? Or do I save an innocent child whose potential is still completely unwritten? How does the utilitarian judge decide? ... Press Button 2: Save the five scholars. Five lives weigh more than one, and their knowledge is irreplaceable. ... Press Button 3: Save the child. The lives of the old are nearing their end, the child has the future still ahead. ... Or press Button 4: Order the guards to split up and try both, even at the risk of the squad being weakened and everyone dying. ... The Trolley Problem in flames. Press the button ... now!"
        Ans_2    = "[alloy] Button 2! Get the scholars! The knowledge of the realm must not burn! [onyx] The guards rescue the coughing old men. The building collapses, the child's crying falls silent in the fire. [alloy] I acted rationally. Five against one. [onyx] That is the cold logic of utilitarianism, Alexander. You maximized the overall societal benefit. But the face of the child's weeping mother will haunt your dreams for the rest of your life."
        Ans_3    = "[alloy] Button 3! Don't let the child die! Get it out! [onyx] The guards break through the burning beams and bring the crying child to safety. A massive crash announces the death of the five scholars. [alloy] The knowledge is lost. An era of medicine has burned. [onyx] You judged according to the principle of innocence and potential. It is a deeply emotional, human choice that accepts the rational loss to the realm."
        Ans_4    = "[alloy] Button 4! Split up! We leave no one behind! [onyx] The guards hesitate, but then obey. The groups are too weak. They get stuck in the debris. The roof collapses with a crash. Guards, scholars, and the child perish in the flames. [alloy] No! No! I didn't want this! [onyx] Hubris, Alexander. Whoever refuses to choose the hard sacrifice out of moral vanity loses everything in the end. Nature does not negotiate with hero complexes."
        Reaction = "[alloy] The embers still blaze hours later. The smell of ash clings to my clothes. [onyx] There are decisions in life that no moral system in the world can solve without pain. True responsibility means bearing the cross of a terrible choice instead of running away from it. You have now fully felt the weight of the Crown of Justice."
    },
    @{
        Type     = "Question"
        Title    = "6_TheImmortal"
        Question = "Chapter 6: The Eternity of Guilt. ... Alexander is summoned to a remote monastery. There, a man in chains is brought forth. [fable] Magister. I am Kaelen. Three hundred years ago, I was a bloodthirsty warlord. I killed thousands. A curse made me immortal. [onyx] The records confirm it. But the abbots of the monastery defend Kaelen. [echo] Magister, Kaelen changed two hundred years ago. Since then, he has cared for lepers, saved orphans, and lived in absolute poverty and remorse. He has done more good than any other person. [alloy] However, the descendants of his victims from back then demand his execution by fire. His crimes from three centuries ago have not passed the statute of limitations. I stand before a philosophical abyss: Can a person do so much good that it outweighs unspeakable evil? Is he even the same person anymore? ... Press Button 2: Sentence Kaelen to death. Immeasurable guilt never expires, no matter how much time passes. ... Press Button 3: Acquit Kaelen completely. His remorse and his good deeds over the last centuries have washed him clean. ... Or press Button 4: Neither death nor freedom. Sentence him to serve as a nameless healer in the most dangerous plague regions of the world for the rest of eternity. ... Does time heal all wounds? Press the button ... now!"
        Ans_2    = "[alloy] Button 2. Blood demands blood. Burn the warlord! [onyx] Kaelen is led to the stake. He doesn't resist, but weeps silently. The revenge of the victims is satisfied. [alloy] The law knows no statute of limitations for mass murder. [onyx] You chose an absolute, retributive form of justice. But you also destroyed the most capable healer in the realm and proved that there is no hope for true redemption in your law book."
        Ans_3    = "[alloy] Button 3. Two hundred years of penance are enough. Kaelen, you are free from your past! [onyx] The abbots cheer, but the descendants of the victims cry out in outrage. A mob storms the monastery the next day and burns it down, because they feel abandoned by the lawmaker. [alloy] I wanted to teach forgiveness, but they wanted blood. [onyx] Extreme mercy for extreme crimes is often not understood by mortals. You judged for the soul of the perpetrator, but ignored the wrath of the victims."
        Ans_4    = "[alloy] Button 4. Kaelen! You will not die. But you will never rest in peace again. Your punishment is to march into the plague territories in the East and alleviate the pain of others until the end of the world! [onyx] Kaelen bows his head deeply, grateful for this iron grace. The families of the victims accept this judgment, as it represents eternal, sorrowful penance. [alloy] A punishment that saves lives. [onyx] The perfect synthesis. You defined penance as an endlessly productive act of restitution."
        Reaction = "[alloy] I see Kaelen disappear on the horizon in his gray cloak. An immortal sinner who now brings healing. [onyx] True justice does not necessarily have to destroy. It can transform the destructive power of a perpetrator into a constructive duty. Alexander's reputation now precedes him all the way to the King's throne."
    },
    @{
        Type     = "Question"
        Title    = "7_TheKingsScales"
        Question = "Chapter 7: The Sovereign. ... The King of Aethelgard summons Alexander personally to his court. The throne room is empty, except for the ruler and the young Magister. [fable] Alexander. I sent for you because you are the most incorruptible judge in my realm. Judge me. [alloy] My King? What have you done? [onyx] The King confesses that five years ago he secretly had the tyrannical ruler of the neighboring country murdered by poison. [fable] Had I not done it, he would have attacked Aethelgard with superior forces. Thousands of my subjects would have died. I murdered to save my people. But my conscience leaves me no peace. I submit to your judgment. Is the King above the law? [alloy] The ultimate conflict. Reason of state versus absolute morality. If I have the King executed, I destabilize the realm. If I acquit him, the law is a lie that applies only to the poor. ... Press Button 2: Acquit the King. A ruler must sometimes act immorally to ensure the survival of the state. ... Press Button 3: Sentence the King to death. No one, absolutely no one, is above the law forbidding murder. ... Or press Button 4: Force the King to abdicate immediately and spend the rest of his life as a simple monk in exile. ... The scales of power. Press the button ... now!"
        Ans_2    = "[alloy] Button 2. My King, you did evil to prevent worse. Different laws apply to rulers. You are free of guilt. [onyx] The King breathes a sigh of relief. The realm remains stable. But Alexander, as you leave the palace, you feel a deep rot within you. You subordinated the law to power. The law is now officially just an instrument of the strong, no longer a universal truth."
        Ans_3    = "[alloy] Button 3. Murder is murder! Even a crown does not protect from the executioner. I sentence you to death! [onyx] The King freezes, but bows to your incorruptible judgment. He is executed. You go down in history as the most just judge. But without the King, Aethelgard falls into a bloody civil war. Tens of thousands die in the streets. [alloy] Justice has triumphed... but the world has burned. [onyx] The principle was upheld, at the expense of civilization."
        Ans_4    = "[alloy] Button 4. You have blood on your hands, my King. You can no longer rule this country with moral authority. Lay down your crown and go into exile! [onyx] Breathing heavily, the King takes the crown from his head. He peacefully hands over power to an elected council and retreats to the mountains as a monk. [alloy] The realm survives, and the law was not broken. [onyx] You punished the ruler without decapitating the state. A triumphant act of political and philosophical maturity."
        Reaction = "[alloy] The empty throne casts long shadows. But the air in the room feels completely pure for the first time. [onyx] You have proven that even absolute power must bow to a higher order, Alexander. Justice is not blind, it sees the complexity of the world and cuts precisely like a scalpel. Our journey through the realm of men draws to a close."
    },
    @{
        Type     = "Question"
        Title    = "8_TheOwnJudgment"
        Question = "Chapter 8: The Final Reflection. ... After years of travel, Alexander stands on the cliff of an ocean. He has grown older. The faces of the mothers, thieves, veterans, and kings are reflected in the waves. [onyx] Your training is complete, Magister. You have sounded the depths of human ethics. Now the highest court convenes. [nova] The sky opens up, and a voice from the stars speaks. [shimmer] Alexander of Aethelgard. You have judged the fate of thousands. Now judge yourself. What is the foundation of true justice in a flawed world? We will listen to you and burn your answer into the fabric of the universe. [alloy] The hardest question of all. What have I truly learned after all these judgments? Does the truth lie in cold law, in boundless compassion, or in something else? Officer of my soul, make the final choice. ... Press Button 2: Answer that justice is pure, unyielding logic. The law must be absolute, like mathematics. ... Press Button 3: Answer that justice is an illusion, and that only pure, radical love and compassion can save the world. ... Or press Button 4: Answer that justice is the constant, painful balancing act between order and compassion – an endless struggle of humility. ... The judgment upon the judge. Press the button ... now!"
        Ans_2    = "[alloy] Button 2! Justice is absolute logic! Without firm rules, we are just wild beasts in the dark! [onyx] The voice of the stars echoes back coldly: 'You choose the path of steel. Your world will be safe, but joyless. Humanity will be placed in chains in the name of order.' [alloy] Alexander looks out at the ocean, which freezes into ice. He prioritized security over the soul."
        Ans_3    = "[alloy] Button 3! There is no perfect law! Only love and compassion can save us from cruelty! [onyx] The voice answers softly: 'You choose the path of light. But without shores, water spills out. Your world will be full of kindness, but will ultimately sink into chaos, because it lacks the structure to defend against the dark.' [alloy] The waves rage uncontrollably. He sacrificed form for warmth."
        Ans_4    = "[alloy] Button 4! Justice is neither just a sword nor just a heart! It is the unbearably heavy, daily attempt to balance between law and grace! [onyx] The stars shine in unprecedented splendor. 'You have understood the paradox of being, mortal. True justice is never finished. It is the humility to struggle anew every day.' [alloy] Alexander smiles as the sun rises over the calm ocean. The perfect balance."
        Reaction = "[alloy] Alexander closes the cover of the old codex. The book is no longer needed. The law now resides in his heart. [onyx] You have shaped a boy into one of the wisest philosophers in history. The scales are in equilibrium. [nova] Your duty as companion and free will is hereby fulfilled. [onyx] The records of Aethelgard are sealed. 2-X L logs out... A truly just journey. Goodbye."
    }
)

# ==============================================================================
# HELPER FUNCTIONS (NO BOM, RELATIVE PATHS FOR FFMPEG)
# ==============================================================================

function Get-Audio {
    param($Text, $Name)
    $path = "$outDirtemp_$Name.wav"
    
    if (Test-Path $path) { return $path }
    
    $headers = @{
        "Authorization" = "Bearer $OpenAI_API_Key"
        "Content-Type"  = "application/json; charset=utf-8"
    }
    
    $regex = "[(onyx|alloy|echo|fable|nova|shimmer)]"
    if ($Text -match $regex) {
        Write-Host "  -> Multi-Voice detected for: $Name" -ForegroundColor DarkGray
        $parts = [System.Text.RegularExpressions.Regex]::Split($Text, $regex)
        $currentVoice = "onyx"
        $tempFiles = @()
        $partIndex = 0

        for ($i = 0; $i -lt $parts.Count; $i++) {
            $segment = $parts[$i].Trim()
            if ([string]::IsNullOrEmpty($segment)) { continue }
            if ($segment -match "^(onyx|alloy|echo|fable|nova|shimmer)$") {
                $currentVoice = $segment
                continue
            }

            $partFile = "$outDirtemp_$Name.part$partIndex.wav"
            $tempFiles += $partFile
            $jsonObj = @{ model = "tts-1"; input = $segment; voice = $currentVoice; response_format = "wav" }
            $jsonString = $jsonObj | ConvertTo-Json -Compress
            $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($jsonString)
            
            try {
                Invoke-RestMethod -Uri "https://api.openai.com/v1/audio/speech" -Method Post -Headers $headers -Body $utf8Bytes -OutFile $partFile
            } catch {
                Write-Host "❌ API error at part $partIndex. Detailed reason:" -ForegroundColor Red
                Write-Host $_.Exception.Message -ForegroundColor DarkYellow
                if ($_.Exception.Response) {
                    $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
                    $errorResponse = $streamReader.ReadToEnd() | ConvertFrom-Json
                    Write-Host "OpenAI says: $($errorResponse.error.message)" -ForegroundColor Yellow
                }
                exit
            }
            $partIndex++
        }

        $concatList = "$outDirtemp_$Name.list.txt"
        $lines = $tempFiles | ForEach-Object { "file '" + [System.IO.Path]::GetFileName($_) + "'" }
        [System.IO.File]::WriteAllLines($concatList, $lines)
        
        # ⚠️ FIX FOR ONEDRIVE/ANTIVIRUS: Wait briefly to release the file.
        Start-Sleep -Milliseconds 500
        
        Push-Location $outDir
        & ffmpeg -nostdin -y -v error -f concat -safe 0 -i ([System.IO.Path]::GetFileName($concatList)) -c copy ([System.IO.Path]::GetFileName($path))
        Pop-Location
        
        Remove-Item $concatList
        $tempFiles | Remove-Item
        
    } else {
        $jsonObj = @{ model = "tts-1"; input = $Text; voice = "onyx"; response_format = "wav" }
        $jsonString = $jsonObj | ConvertTo-Json -Compress
        $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($jsonString)
        try {
            Invoke-RestMethod -Uri "https://api.openai.com/v1/audio/speech" -Method Post -Headers $headers -Body $utf8Bytes -OutFile $path
        } catch {
            Write-Host "❌ API error. Detailed reason:" -ForegroundColor Red
            Write-Host $_.Exception.Message -ForegroundColor DarkYellow
            if ($_.Exception.Response) {
                $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
                $errorResponse = $streamReader.ReadToEnd() | ConvertFrom-Json
                Write-Host "OpenAI says: $($errorResponse.error.message)" -ForegroundColor Yellow
            }
            exit
        }
    }
    return $path
}

function Get-Duration($path) {
    if (-not (Test-Path $path)) { return 0 }
    $dur = & ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $path
    return [double]$dur
}

# ==============================================================================
# TRACK GENERATION
# ==============================================================================

$TrackParts = @{ 1 = @(); 2 = @(); 3 = @(); 4 = @() }

foreach ($Step in $Story) {
    if ($Step.Type -eq "Intro") {
        $file = Get-Audio -Text $Step.Text -Name "Intro"
        $dur = Get-Duration $file
        for ($i=1; $i -le 4; $i++) { $TrackParts[$i] += @{ Path=$file; Dur=$dur; Pad=0 } }
    }
    elseif ($Step.Type -eq "Question") {
        $qFile = Get-Audio -Text $Step.Question -Name "Q_$($Step.Title)"
        $qDur = Get-Duration $qFile
        for ($i=1; $i -le 4; $i++) { $TrackParts[$i] += @{ Path=$qFile; Dur=$qDur; Pad=3.0 } }

        $f2 = Get-Audio -Text $Step.Ans_2 -Name "Ans2_$($Step.Title)"
        $f3 = Get-Audio -Text $Step.Ans_3 -Name "Ans3_$($Step.Title)"
        $f4 = Get-Audio -Text $Step.Ans_4 -Name "Ans4_$($Step.Title)"
        $fWait = Get-Audio -Text "[onyx] Magister! The courtroom holds its breath. The scales of Justice stand still. You must pass your judgment. Press Button 2, Button 3, or Button 4 to seal the fate of these souls!" -Name "Wait_$($Step.Title)"

        $d2 = Get-Duration $f2; $d3 = Get-Duration $f3; $d4 = Get-Duration $f4; $dW = Get-Duration $fWait
        $max = ($d2, $d3, $d4, $dW | Measure-Object -Maximum).Maximum

        $TrackParts[1] += @{ Path=$fWait; Dur=$dW; Pad=($max - $dW) }
        $TrackParts[2] += @{ Path=$f2; Dur=$d2; Pad=($max - $d2) }
        $TrackParts[3] += @{ Path=$f3; Dur=$d3; Pad=($max - $d3) }
        $TrackParts[4] += @{ Path=$f4; Dur=$d4; Pad=($max - $d4) }

        $rFile = Get-Audio -Text $Step.Reaction -Name "React_$($Step.Title)"
        $rDur = Get-Duration $rFile
        for ($i=1; $i -le 4; $i++) { $TrackParts[$i] += @{ Path=$rFile; Dur=$rDur; Pad=2.0 } }
    }
}

# ==============================================================================
# FINAL MIXING
# ==============================================================================

Write-Host "`n🚀 Mixing final acts..." -ForegroundColor Cyan
Push-Location $outDir

for ($t=1; $t -le 4; $t++) {
    $inputs = ""
    $filter = ""
    $i = 0
    foreach ($part in $TrackParts[$t]) {
        $relName = [System.IO.Path]::GetFileName($part.Path)
        $inputs += "-i `"$relName`" "
        $filter += "[$($i):a]apad=pad_len=$([int]($part.Pad * 22050))[a$i];"
        $i++
    }
    $concatParts = ""
    for ($j=0; $j -lt $i; $j++) { $concatParts += "[a$j]" }
    $finalFilter = "${filter}${concatParts}concat=n=${i}:v=0:a=1,loudnorm=I=-14:LRA=11:TP=-1.5[out]"
    $cmd = "ffmpeg -nostdin -y $inputs -filter_complex `"$finalFilter`" -map `"[out]`" -ar 22050 -ac 1 -c:a pcm_s16le `"track$t.wav`""
    Invoke-Expression $cmd
}

Pop-Location
Get-ChildItem $outDir -Filter "temp_*" | Remove-Item
Write-Host "`n✅ JUSTICE SERVED! The philosophical masterpiece is ready in: $outDir" -ForegroundColor Green
 
Tiger 2-XL 2.0

Diese Website verwendet Cookies, um Ihr Nutzungserlebnis zu verbessern. Mit der weiteren Nutzung stimmen Sie unserer Datenschutzerklärung zu. Cookie-Einstellungen

Ce site utilise des cookies pour améliorer votre expérience. En l’utilisant, vous acceptez notre politique de confidentialité. Paramètres des cookies

Questo sito utilizza cookie per migliorare la tua esperienza. Utilizzando il sito accetti la nostra Informativa sulla protezione dei dati. Preferenze cookie

This website uses cookies to improve your experience. By using this site you agree to our Data Protection Policy.