[
  {
    "path": ".gitignore",
    "content": "~*\r\n~*.*"
  },
  {
    "path": "MIT-LICENSE.txt",
    "content": "Copyright (c) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "About Hacksby\n=============\n\n### Hacksby = Hack-a-Furby project\n\nHasbro's Furby toy (year 2012 model) uses audio protocol to communicate with other\nnearby Furbys and with the official 'Furby' applications for iOS and Android.\n\nThis project is an educational attempt to analyze and re-create the audio protocol\nand communicate with Furby using computer in a search for some easter eggs or\notherwise undocumented features.\n\nAt its current state, the project includes:\n\n 1. Description on the audio protocol (see below)\n 2. [An incomplete] description of various Furby commands\n 3. An Perl library and scripts that can generate and play WAV files\n    with arbitrary commands (to talk to Furby)\n 4. A script that can decode commands from a provided audio stream\n    (to interpret Furby's responses).\n\n\nDisclaimer\n==========\n\n### This information is provided for personal educational purposes only.\n### The author does not guarantee the accuracy of this information.\n### By using the provided information, libraries or software, you solely take the risks of damaging your hardware or your ears.\n\nSee `MIT-LICENSE.txt` for more information.\n\n\nAudio Protocol\n==============\n\nFurby audio protocol uses high-pitch frequencies to encode special commands (command\nis an integer number in [0..1023] range). Furby decodes such commands using its built-in\nmicrophone and may respond to some of them. When an event occurs (Furby pronounces\nsome phrase or performs an action), he can also emit such a command in addition to\nan audible sound. This feature is used in the official application 'Translator'\nmode, when it recognizes what Furby said and provides an instant translation.\n\nEach command is divided into two packages with 0.5 sec gap in between.\nThe first package carries the higher 5 bits of the command number, and the second one\ncarries the lower 5 bits. \n\nIf you record the responce from Furby or from iOS application and view its spectrum,\nYou will see that each packet looks like this: \n\n    2  --------##--------------------------------------##----\n    3  ----##----------------------##--##----------##--------\n    X  --##--##--##--##--##--##--##--##--##--##--##--##--##--\n    1  --------------------##--------------##----------------\n    0  ------------##--##------##--------------##------------\n\nThe total length of the packet is 0.5 second. Here `X` is a central frequency (17500 Hz),\nand `0`, `1`, `2` and `3` are the data frequencies. The distance between each adjacent frequency\nis approx. 557 Hz.\n\nThe central frequency carries no data and was likely introduce to aid in packet decoding.\nThe other four frequencies carry data. If one writes down the packet depicted above as a number in the\nquaternary numeral system using 0, 1, 2, 3 digits after the name of each frequency, he would\nget the following number: `3200 1033 1032` (for clarity, the number is separated into three quadruplets\neach representing a byte).\n\nThe first byte, `3200`, if written in the binary form, will look like this: `11 1 00000`, where the first\ntwo bits are always `11`, the second bit will be `0` for the first packet and `1` for the second, and\nthe remainder `00000` represents the 5 data bits themselves.\n\nThe second byte, `1033`, depends on the data bits and is used as a checksum (original algorithm is unknown).\nThe `lib/Furby/Packet.pm` file lists all 64 checksums (32 for the first packet and 32 for the second) needed\nto reconstruct any arbitrary command in [0..1023] range.\n\nThe last byte, `1032`, is always the same.\n\nTo eliminate ticks when playing back such audio packets, the original waveform uses smooth changes in\nfrequency between each data tone. Also, such frequencies are not noticable if combined with pretty loud\naudible responses Furby generates at the same time.\n\n\nCommands\n========\n\nBased on initial research, a list of known commands (or events) and their descriptions is provided in\n`lib/Furby/Commands.pm` and `lib/Furby/Dictionary.pm`. The list is incomplete and the already existing\ndescriptions may be inaccurate.\n\nThe task of interpreting the meaning of the commands is complicated further by the fact that Furby\ncan be in one of 6 personalities, and depending on its current personality, may respond to commands\ndifferently and produce different events on its own.\n\n### Furby Personality\n\nWhen Furby understands a command, it will respond back with his current personality id.\nKnown personalities (as listed in official Android application) are:\n\n  1. Princess (command id `901`) — a lovable one\n  2. Diva (command id `902`) — a musical one\n  3. Warrior (command id `903`) — also known as 'evil'\n  4. Joker (command id = `904`) — also known ad 'mad' or 'freaky'\n  5. Gossip Queen (command id `905`) — a chatty one\n\nThere is no known way to instantly change Furby's personality via some special command. Sending a command\nwith the personality id back to Furby seems to produce no effect.\n\n### Communication Mode\n\nWhen Furby chats or performs any action, it will ignore any commands sent to him.\nSo one needs to ensure Furby is listenting before sending any command. Luckily, there's command `820`\nwhich will put him into such listening mode for one minute (during this period, Furby just stay awake\nand listen for other commands). This comamnd is used in the official applications and is sent every 40\nseconds or so to keep Furby listen and stop doing silly things.\n\nUnfortunately, even if one sends this command periodically, Furby will go into deep sleep mode after\n10 minutes of inactivity. The only way to prevent it from sleep is turning or flippng Furby periodically\nso that its orientation sensor detects the movement.\n\nTools\n=====\n\nTools are located in `bin` directory.\n\n### Send Commands to Furby\n\n**CAUTION: setting the volume too high while playing back Furby commands may damage your ears!**\n\nPut your Furby near the speaker connected to your computer (or connect an earbud to a headphone jack\nand put it in front of Furby). Turn the volume all the way down. Wake up Furby and wait till it listens quietly.\nRun the command below:\n\n    perl furby-send.pl 350\n\nTurn the volume up a little and run the command again to see if Furby recognizes it\n(he should chew and then say something like \"mmm, yum!\"). If it doesn't, turn the volume up a bit and repeat\nthe procedure until it does.\n\n#### Troubleshooting\n\nIf you can hear a discomforting high-pitch noise when the command plays but Furby doesn't repsond,\nthen something is wrong. Try putting Furby closer to the speaker or the earbud and make sure Furby doesn't\ndo anything on his own while you play back the command. Normally, Furby should pick up the command even if\nyou are not hearing it yet.\n\n#### Interactive Mode\n\nYou can also run the tool in interactive mode:\n\n    perl furby-send.pl 350 --interactive\n\nAfter playing back the first command (`350`), the script will wait for your input. You can just press Enter to\nplay the next command in the range, or input a command number to play, or input anything else that doesn't\nevaluate to a number (for example, `r`) to repeat the last command. This mode will allow you to explore the\nFurby reactions to different commands.\n\nThe generated WAV file is saved as `out.wav` in the current directory. Under Windows, the playback is performed\nusing the provided command-line utility (`bin/win32/dsplay.exe`). Under Unix / Mac, an attempt to use\nalready existing console players is used (but not tested).\n\n\n### Listen and Decode Furby Commands\n\nThe provided `furby-decode.pl` decodes the RAW PCM data from STDIN and displays any commands it decodes.\nThe only supported format is 44.1KHz mono 16bit signed PCM format.\n\nYou can pre-record a WAV file using your microphone and then decode it using this tool like this:\n\n    perl furby-decode.pl < record.wav\n\nor pipe in PCM data from a streaming application or virtual device. Under Windows, a binary tool is included \n(`bin/win32/rec_stdout.exe`) which records data from a default input source and constantly streams it to STDOUT.\nYou can use it like this:\n\n    win32\\rec_stdout.exe | perl furby-decode.pl\n\nor just run\n\n    furby-listen.bat\n\nBefore you run this command, make sure you have a microphone plugged in, that it is selected in Sound settings\nas a default capturing device and put your microphone close to Furby.\n\nThe sound capture hasn't been tested on platforms other than Windows.\n\n#### Troubleshooting\n\nSome microphones (especially the ones integrated into web cameras) use a low-pass filter that would essentially\nwipe out all frequencies that are used to transmit commands. If your decoding doesn't work, try recording\nthe audio sample into the 44.1KHz mono 16bit signed PCM WAV file (touch Furby's head or tum to make sure it responds\nto your interaction and thus emits an event command), then open the file in a sound editor like Audacity,\nmake sure the audio format is correct, switch to a spectrum view and see if there are any frequencies recorded\naround the 17.5KHz range (you will clearly see if there are command packets there).\n\n\n### Feedback\n\nFeedback and any further research results are always welcome.\n"
  },
  {
    "path": "bin/.gitignore",
    "content": "out.*\n"
  },
  {
    "path": "bin/draw-fft.pl",
    "content": "#!/usr/bin/perl\n\n# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\nuse strict;\n\n# only raw 16bit signed PCM data is supported\n\nuse GD;\nuse Math::FFT;\n\nmy $filename = $ARGV[0];\n\nif ($filename eq '') {\n    print \"Usage: perl $0 filename.raw\\n\";\n    exit(1);\n}\n\nif (!-f $filename) {\n    print \"File '$filename' doesn't exist\\n\";\n    exit(1);\n}\n\nmy $base_freq = {\n    '0' => 16386,\n    '1' => 16943,\n    'X' => 17500, # base frequency, used to construct raw commands (delta is approx 557 Hz) // was 17498 with 556 Hz delta\n    '3' => 18057,\n    '2' => 18614,\n};\n\nmy $allowed_deviation = abs($base_freq->{'X'} - $base_freq->{'1'}) * 0.2;\n\nmy $buffer;\nmy $read_block_size = 10000;\nmy $sample_rate = 44100;\nmy $size_in_samples = 256;\nmy $expand_index = 5;\nmy $max_image_width = 10000;\n\nmy $fft_min_freq = 0;\nmy $fft_max_freq = $sample_rate / 2;\nmy $fft_spectrum_size = int($size_in_samples / 2) + 1;\nmy $fft_freq_delta = ($fft_max_freq - $fft_min_freq) / ($fft_spectrum_size - 1);\n\nmy $spectrum_first_idx = int($base_freq->{'0'} / $fft_freq_delta) - $expand_index;\n$spectrum_first_idx = 0 if $spectrum_first_idx < 0;\n\nmy $spectrum_last_idx = int(($base_freq->{'2'} + $fft_freq_delta / 2) / $fft_freq_delta) + $expand_index;\n$spectrum_last_idx = $fft_spectrum_size - 1 if $spectrum_first_idx > $fft_spectrum_size - 1;\n\nmy $image_height = $spectrum_last_idx - $spectrum_first_idx + 1;\n\nmy @spectrum_frequencies;\nfor my $y ($spectrum_first_idx..$spectrum_last_idx) {\n    $spectrum_frequencies[$y] = $fft_min_freq + $y * $fft_freq_delta;\n}\n\nmy $image_width = int((-s $filename) / ($size_in_samples * 2));\nif ($image_width > $max_image_width) {\n    $image_width = $max_image_width;\n    print \"Image would be too big; it will be trimmed to first $max_image_width spectrum pxiels\\n\";\n}\n\nmy $image = new GD::Image($image_width, $image_height, 1);\n\nmy $base_freq_colors = {\n    '0' => $image->colorResolve(255,   0, 255),\n    '1' => $image->colorResolve(  0, 204, 255),\n    'X' => $image->colorResolve(255,   0,   0),\n    '3' => $image->colorResolve(  0, 255,   0),\n    '2' => $image->colorResolve(255, 255,   0),\n};\n\nopen(IN, $filename);\n#sysread(IN, $_, 56); # skip RIFF header\nread_from_handle(*IN);\nclose(IN);\n\nmy $x = 0;\n\nmy @samples;\nmy $buffer_leftover;\nsub read_from_handle {\n    my $handle = shift;\n    while ((my $n = sysread($handle, $buffer, $read_block_size)) > 0) {\n        #print \"Read $n bytes\\n\";\n        $buffer = $buffer_leftover.$buffer if $buffer_leftover; # prepend leftover byte, if any\n        while (length($buffer) >= 2) {\n            my $sample = unpack('s<', substr($buffer, 0, 2, ''));\n            push(@samples, $sample);\n            if ($size_in_samples == @samples) {\n                analyze_samples();\n                undef @samples;\n\n                $x++;\n                last if $x > $max_image_width - 1;\n            }\n        }\n        $buffer_leftover = $buffer; # potentially there can be a single byte that we need to preserve for next iteration\n    }\n}\n\nsub analyze_samples {\n    my $fft = new Math::FFT(\\@samples);\n    my $spectrum = $fft->spctrm(window => 'hann');\n\n    my $avg = 0;\n    my $max = 0;\n    my $max_y = -1;\n    my $freq = 0;\n    for my $y ($spectrum_first_idx..$spectrum_last_idx) {\n        $avg += $spectrum->[$y];\n        if ($spectrum->[$y] > $max) {\n            $max = $spectrum->[$y];\n            $max_y = $y;\n            $freq = $spectrum_frequencies[$y];\n        }\n    }\n    $avg = $avg / $fft_spectrum_size;\n    my $contrast = ($avg == 0) ? $fft_spectrum_size : $max / $avg;\n\n    my $known_frequency_key;\n    foreach my $key (keys %$base_freq) {\n        if (abs($base_freq->{$key} - $freq) < $allowed_deviation) {\n            $known_frequency_key = $key;\n            last;\n        }\n    }\n\n    for my $y ($spectrum_first_idx..$spectrum_last_idx) {\n        my $color = $max > 0 ? int($spectrum->[$y] / $max * 255) : 0;\n        $color = 255 if $color > 255;\n        #$color = int($color * 0.5);\n        if ($contrast < ($fft_spectrum_size / 3.5)) {\n           $color = $color * 0.2;\n           $color = $image->colorResolve($color, $color, $color)\n        } else {\n            $color = ($y == $max_y) ? $image->colorResolve(255, 255, 255) : $image->colorResolve($color, $color, $color);\n            if (defined $known_frequency_key && ($y == $max_y)) {\n                $color = $base_freq_colors->{$known_frequency_key};\n            }\n        }\n        $image->setPixel($x, $image_height - 1 - ($y - $spectrum_first_idx), $color);\n    }\n}\n\nprint \"Saving out.png...\\n\";\nopen(OUT, \">out.png\");\nbinmode OUT;\nprint OUT $image->png;\nclose OUT;\n"
  },
  {
    "path": "bin/furby-decode.pl",
    "content": "#!/usr/bin/perl\n\n# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\nuse strict;\n\n# only raw 16bit signed PCM data is supported\n\nBEGIN {\n    use File::Spec::Functions qw(rel2abs catfile);\n    use File::Basename;\n    unshift(@INC, catfile(dirname(rel2abs($0)), '../lib'));\n}\n\nuse Math::FFT;\n\nuse Furby::Audio;\nuse Furby::Command;\nuse Furby::Packet;\n\n$| = 1; # prevent STDOUT buffering\n\n$SIG{INT} = sub {\n    print \"SIGINT caught, stopping...\\n\";\n    exit(2);\n};\n\nmy $central_freq = Furby::Audio::base_freq('X');\nmy $next_to_central_freq = Furby::Audio::base_freq('1');\nmy $lowest_freq = Furby::Audio::base_freq('0');\nmy $highest_freq = Furby::Audio::base_freq('2');\n\nmy $allowed_deviation = abs($central_freq - $next_to_central_freq) * 0.2;\n\nmy $buffer;\nmy $read_block_size = 10000;\nmy $sample_rate = 44100;\nmy $size_in_samples = 256;\nmy $expand_index = 5;\nmy $contrast_ratio = 3.5;\n\nmy $fft_min_freq = 0;\nmy $fft_max_freq = $sample_rate / 2;\nmy $fft_spectrum_size = int($size_in_samples / 2) + 1;\nmy $fft_freq_delta = ($fft_max_freq - $fft_min_freq) / ($fft_spectrum_size - 1);\n\nmy $spectrum_first_idx = int($lowest_freq / $fft_freq_delta) - $expand_index;\n$spectrum_first_idx = 0 if $spectrum_first_idx < 0;\n\nmy $spectrum_last_idx = int(($highest_freq + $fft_freq_delta / 2) / $fft_freq_delta) + $expand_index;\n$spectrum_last_idx = $fft_spectrum_size - 1 if $spectrum_first_idx > $fft_spectrum_size - 1;\n\nmy @spectrum_frequencies;\nfor my $y ($spectrum_first_idx..$spectrum_last_idx) {\n    $spectrum_frequencies[$y] = $fft_min_freq + $y * $fft_freq_delta;\n}\n\nmy $char_buffer;\nmy $char = undef;\nmy $prev_was_X = undef;\nmy $packet1 = -1;\n\nmy $filename = $ARGV[0];\n\nmy $use_stdin = ($filename eq '');\n\nif ($use_stdin) {\n    my $h = *STDIN;\n    while (1) {\n        read_from_handle($h);\n    }\n} else {\n    if (!-f $filename) {\n        print \"File '$filename' doesn't exist\\n\";\n        exit(1);\n    }\n    open(IN, $filename);\n    read_from_handle(*IN);\n    close(IN);\n}\n\nmy @samples;\nmy $buffer_leftover;\nsub read_from_handle {\n    my $handle = shift;\n    while ((my $n = sysread($handle, $buffer, $read_block_size)) > 0) {\n        #print \"Read $n bytes\\n\";\n        $buffer = $buffer_leftover.$buffer if $buffer_leftover; # prepend leftover byte, if any\n        while (length($buffer) >= 2) {\n            my $sample = unpack('s<', substr($buffer, 0, 2, ''));\n            push(@samples, $sample);\n            if ($size_in_samples == @samples) {\n                analyze_samples();\n                undef @samples;\n            }\n        }\n        $buffer_leftover = $buffer; # potentially there can be a single byte that we need to preserve for next iteration\n    }\n}\n\nsub analyze_samples {\n    my $fft = new Math::FFT(\\@samples);\n    my $spectrum = $fft->spctrm(window => 'hann');\n\n    my $avg = 0;\n    my $max = 0;\n    my $max_y = -1;\n    my $freq = 0;\n    for my $y ($spectrum_first_idx..$spectrum_last_idx) {\n        $avg += $spectrum->[$y];\n        if ($spectrum->[$y] > $max) {\n            $max = $spectrum->[$y];\n            $max_y = $y;\n            $freq = $spectrum_frequencies[$y];\n        }\n    }\n    $avg = $avg / $fft_spectrum_size;\n    my $contrast = ($avg == 0) ? $fft_spectrum_size : $max / $avg;\n\n    if ($contrast >= ($fft_spectrum_size / $contrast_ratio)) {\n        foreach my $key (Furby::Audio::base_freq_keys) {\n            if (abs(Furby::Audio::base_freq($key) - $freq) < $allowed_deviation) {\n\n                my $is_X = $key eq 'X';\n\n                if (!$is_X) {\n                    $char = $key if ($char eq '3' && $key eq '2');\n                    $char = $key if ($char eq '1' && $key eq '0');\n                    $char = $key unless defined $char;\n                    #print \"{$char}\";\n                }\n\n                if ($prev_was_X ne $is_X) {\n                    if ($is_X) {\n                        if ($char ne '') {\n                            $char_buffer .= $char;\n                            #print \"( $char )\";\n                            print \"$char\";\n                        }\n                        $char = undef;\n                        print \"-\";\n                        analyze_char_buffer();\n                    }\n                    $prev_was_X = $is_X;\n                }\n\n                last;\n            }\n        }\n    }\n} \n\nsub analyze_char_buffer {\n    while (1) {\n        last unless $char_buffer =~ m/^.*?(\\d{4})(\\d{4})1032/;\n        my $packet = Furby::Packet::parse(\"$1$2\");\n        print \"[ $1-$2-1032 = $packet ]\\n\";\n        #print \"( Packet = $packet )\";\n        if ($packet != -1) {\n            if ($packet < 32) {\n                $packet1 = $packet;\n            } else {\n                if ($packet1 != -1) {\n                    my $command = $packet1 << 5 | ($packet - 32);\n                    my $description = Furby::Command::description($command) || '?';\n                    print \"\\n($command) $description\\n\\n\";\n                }\n                $packet1 = -1;\n            }\n        }\n\n        $char_buffer =~ s/^.*?(\\d{4})(\\d{4})1032//;\n\n        $char = undef;\n        $prev_was_X = undef;\n    }\n}\n"
  },
  {
    "path": "bin/furby-listen.bat",
    "content": "@echo off\r\necho Listening for Furby commands...\r\nwin32\\rec_stdout.exe | perl furby-decode.pl"
  },
  {
    "path": "bin/furby-send.bat",
    "content": "@perl furby-send.pl 0 --interactive"
  },
  {
    "path": "bin/furby-send.pl",
    "content": "#!/usr/bin/perl\n\n# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\nuse strict;\n\nBEGIN {\n    use File::Spec::Functions qw(rel2abs catfile);\n    use File::Basename;\n    unshift(@INC, catfile(dirname(rel2abs($0)), '../lib'));\n}\n\nuse Furby::Audio;\n\nif ($^O eq 'MSWin32') {\n    require Win32::Sound;\n}\n\n$| = 1; # disable STDOUT buffering\n\n$SIG{INT} = sub {\n    print \"SIGINT caught, stopping...\\n\";\n    exit(2);\n};\n\nmy $filename = 'out.wav';\n\nmy $min_number = 0;\nmy $max_number = 1023;\n\nmy $number = $ARGV[0];\nmy $interactive = $ARGV[1] eq '--interactive';\n\nif ($number eq '' or !$interactive && $ARGV[1] ne '') {\n    print \"Usage: perl $0 <command> [--interactive]\\n\";\n    exit(1);\n}\n\nif ($number < $min_number or $number > $max_number) {\n    print \"Command number must be in [$min_number..$max_number] range\\n\";\n    exit(1);\n}\n\nif (!$interactive) {\n    Furby::Audio::generate_wav($number, $filename);\n    play_wav($filename);\n} else {\n    while ($number <= $max_number) {\n        Furby::Audio::generate_wav($number, $filename);\n        print \"Command $number ... \";\n        play_wav($filename);\n        if ($number == $max_number) {\n            print \"Done\\n\";\n            last;\n        } else {\n            print \"Next: \";\n            my $s = <STDIN>;\n            chomp $s;\n            if ($s eq '') {\n                $number++;\n            } else {\n                $number = $s if ($s + 0 eq $s && $s >= $min_number && $s <= $max_number);\n            }\n        }\n    }\n}\n\nsub play_wav {\n    my ($filename) = @_;\n\n    my $command = 'aplay'; # assube we're on some Linux desktop flavor\n\n    if ($^O eq 'MacOS') {\n        $command = 'afplay';\n    }\n\n    if ($^O eq 'MSWin32') {\n        $command = '\"'.catfile(dirname(rel2abs($0)), 'win32/dsplay.exe').'\"';\n    }\n\n    system(\"$command $filename\");\n}\n"
  },
  {
    "path": "bin/win32/README.md",
    "content": "See the following documents for more information on the binary utilities:\n\n../src/dsplay/README.md\n../src/rec_stdout/README.md"
  },
  {
    "path": "lib/Furby/Audio.pm",
    "content": "# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\npackage Furby::Audio;\n\nuse strict;\n\nuse Audio::Wav;\n\nuse Furby::Packet;\n\nmy $base_freq = {\n    '0' => 16386,\n    '1' => 16943,\n    'X' => 17500, # base frequency, used to construct raw commands (delta is approx 557Hz)\n    '3' => 18057,\n    '2' => 18614,\n};\n\nmy $PI = (22 / 7) * 2;\n\nmy $bits_sample = 16;\nmy $sample_rate = 44100;\nmy $top_freq = $sample_rate / 2;\n\nmy $base_freq_length          = 0.016; # 16ms\nmy $xfade_length              = 0.004; #  4ms\nmy $lead_silence_gap_length   = 0.005; #  5ms\nmy $lead_length               = 0.005; #  5ms\nmy $silence_gap_length        = 0.5 - $lead_length * 2; # 0.5s in total\nmy $xfade_volume_samples      = 220;\nmy $lead_xfade_volume_samples = $sample_rate * $lead_length; # entire length\n\nsub base_freq {\n    my $char = shift;\n    die \"Unknown frequency name '$char'\" unless exists $base_freq->{$char};\n    return $base_freq->{$char};\n}\n\nsub base_freq_keys {\n    return keys %$base_freq;\n}\n\nsub generate_wav {\n    my ($command, $filename) = @_;\n\n    my $wav = new Audio::Wav;\n\n    my $wav_output = $wav->write($filename, {\n        'bits_sample'   => $bits_sample,\n        'sample_rate'   => $sample_rate,\n        'channels'      => 1,\n    });\n\n    my $data = Furby::Packet::make($command);\n    my ($str1, $str2) = split(' ', $data);\n\n    add_silence($wav_output, $lead_silence_gap_length);\n    add_packet($wav_output, $str1);\n    add_silence($wav_output, $silence_gap_length);\n    add_packet($wav_output, $str2);\n    add_silence($wav_output, $lead_silence_gap_length);\n\n    $wav_output->finish();\n}\n\nsub add_packet {\n    my ($wav_output, $str) = @_;\n\n    # remove irrelevant splitter symbols\n    $str =~ s/[^0123]//g;\n\n    # interleave all digits with the base frequency:\n    # 0123 => X0X1X2X3X\n    my @a = ('', split(//, $str), '');\n    add_raw_packet($wav_output, join('X', @a));\n}\n\nsub add_raw_packet {\n    my ($wav_output, $str) = @_;\n    my @a = split(//, $str);\n    my $max_idx = $#a;\n\n    add_sine($wav_output, $top_freq, base_freq($a[0]), $lead_length, $lead_xfade_volume_samples, $xfade_volume_samples);\n\n    for my $i (0..$max_idx) {\n        add_sine($wav_output, base_freq($a[$i]), base_freq($a[$i]), $base_freq_length, $xfade_volume_samples, $xfade_volume_samples);\n        if ($i < $max_idx) {\n          add_sine($wav_output, base_freq($a[$i]), base_freq($a[$i+1]), $xfade_length, $xfade_volume_samples, $xfade_volume_samples);\n        }\n    }\n\n    add_sine($wav_output, base_freq($a[$max_idx]), $top_freq, $lead_length, $lead_xfade_volume_samples, $xfade_volume_samples);\n}\n\nsub add_sine {\n    my ($wav_output, $hz1, $hz2, $length, $fadein_volume_samples, $fadeout_volume_samples, $volume) = @_;\n    $hz2 = ($hz1 + $hz2) / 2; # fft magic\n    $volume = 1 unless defined $volume;\n    $length *= $sample_rate;\n    my $max_no = (2 ** $bits_sample) / 2 - 1;\n    my $cur_vol = 0;\n    my $phase;\n    for my $pos (0 .. $length-1) {\n        my $time = $pos / $sample_rate;\n        my $hz = $pos / ($length - 1) * ($hz2 - $hz1) + $hz1;\n        $phase = $PI * $time * $hz;\n    \n        $cur_vol = $volume;\n        if ($fadein_volume_samples > 0) {\n            if ($pos < $fadein_volume_samples) {\n                $cur_vol = $pos / ($fadein_volume_samples + 1) * $cur_vol;\n            }\n        }\n        if ($fadeout_volume_samples > 0) {\n            if ($length - 1 - $pos < $fadeout_volume_samples) {\n                $cur_vol = ($length - 1 - $pos) / ($fadeout_volume_samples + 1) * $cur_vol;\n            }\n        }\n\n        $wav_output->write(sin($phase) * $max_no * $cur_vol);\n    }\n}\n\nsub add_silence {\n    my ($wav_output, $length) = @_;\n    $length *= $sample_rate;\n    for my $pos (0..$length) {\n        $wav_output->write(0);\n    }\n}\n\n1;"
  },
  {
    "path": "lib/Furby/Command/Dictionary.pm",
    "content": "# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n# The dictionary data was taken from the official Furby application for Android, (C) Hasbro, Inc.\n# https://play.google.com/store/apps/details?id=com.hasbro.furby\n\npackage Furby::Command::Dictionary;\n\nmy $dictionary = {\n      '0' => \"Me like. / Kah toh-loo.\",\n      '1' => \"Me like you. / Kah toh-loo oo-nye.\",\n      '2' => \"Upside down! / Oo-tye nah-bah!\",\n      '3' => \"Listen. / Ay-ay-lee-koo.\",\n      '4' => \"Singing time. / Wee-tee toh-toh.\",\n      '5' => \"Big joke! / Dah loo-loo!\",\n      '6' => \"More talk. / Koh-koh noo-noo.\",\n      '7' => \"Yes. Thank you. / Ee-tay. Dah-kah-oo-nye.\",\n      '8' => \"Me like talk! / Kah toh-loo noo-noo!\",\n      '9' => \"Talk big fun! / Noo-noo dah doo-ay.\",\n     '10' => \"Yes no yes no. / Ee-tay boo ee-tay boo.\",\n     '11' => \"Good food! / Ee-day ah-tah!\",\n     '12' => \"More! More! More! / Koh-koh! Koh-koh! Koh-koh!\",\n     '13' => \"Good morning! / Dah-ay-loh-oo-tye!\",\n     '14' => \"Good game! / Ee-day doo-ay-loo-lah!\",\n     '15' => \"Me see you. / Kah ay-ay oo-nye.\",\n     '16' => \"Me love you. / Kah may-may oo-nye.\",\n     '17' => \"Love friend! / May-may noo-lah!\",\n     '18' => \"Big happy. / Dah noo-loo.\",\n     '19' => \"Pet more. / Ah-may koh-koh.\",\n     '20' => \"Big noise! / Dah bah-boo!\",\n     '21' => \"Please hug me. / Doo-moh may-lah kah.\",\n     '22' => \"Huh? Good morning. / Doo? Dah-ay-loh-oo-tye.\",\n     '23' => \"Little noise. / Dee bah-boo.\",\n     '24' => \"Tickle. / Nee-tye.\",\n     '25' => \"Noise! / Bah-boo!\",\n     '26' => \"Happy. / Noo-loo.\",\n     '27' => \"Hug Me / May-lah Kah.\",\n     '28' => \"Hey! No! / Hey! Boo!\",\n     '29' => \"Me very happy, yeah! / Kah mee-mee noo-loo, wah!\",\n     '30' => \"Me like. Again please. / Kah toh-loo. Koh-koh doo-moh.\",\n     '31' => \"Uh-oh! Me no like.Uh-oh! / Kah no toh-loo.\",\n     '32' => \"Me scared! / Kah dah-boh-bay!\",\n     '33' => \"Me! / Kahhh!\",\n     '34' => \"Me down! / Kah nah-bah!\",\n     '35' => \"Me Me Me! / Kah kah kahhhh!\",\n     '36' => \"No feel good! / Boo koo-doh e-day!\",\n     '37' => \"Yeah! Yes! / Wah! Ee-tay!\",\n     '38' => \"Dance! / Noh-lah!\",\n     '39' => \"Me like dance. / Kah toh-loo noh-lah.\",\n     '40' => \"Like, dance baby! / Tay, noh-lah bay-bee!\",\n    #...\n     '42' => \"No joke. / Boo loo-loo.\",\n     '43' => \"JokeJokeJokeJoke! / LooLooLooLooLoo!\",\n     '44' => \"Genius! / Dah-way!\",\n     '45' => \"Pretty noise! / Ee-kah bah-boo!\",\n     '46' => \"Big no! / Dah-boo!\",\n     '47' => \"Me hungry! / Kah Ay-tay!\",\n     '48' => \"No hungry! / Boo ay-tay!\",\n     '49' => \"Me no happy. / Kah boo noo-loo.\",\n     '50' => \"Me very hungry! / Kah mee-mee ay-tay!\",\n     '51' => \"Please, hungry! / Doo-moh, ay-tay!\",\n     '52' => \"Please talk. / Doo-moh noo-noo.\",\n     '53' => \"Me tired. / Kah way-loh.\",\n     '54' => \"Where are you? / Oo-nye doo?\",\n     '55' => \"Funny talk. / Doo-loo noo-noo.\",\n     '56' => \"Yeah, yeah, yeah?! / Wah, wah, wah?!\",\n     '57' => \"Over, over, dizzy, me change! / Oo-bah, oo-bah, ay-way, kah boo-tay!\",\n     '58' => \"Like singing! / Toh-loo wee-tee!\",\n     '59' => \"Like, me LIKE! / Tay, kah TAY!\",\n     '60' => \"Like, me hear you! / Tay, mah ay-ay-lee-koo oo-nye!\",\n     '61' => \"Like, listen! / Tay, ay-ay-lee-koo!\",\n     '62' => \"Me dance! / Kah noh-lah!\",\n     '63' => \"Hmm? Okay! / Doo? Oh-kay!\",\n     '64' => \"Huh? What? No! / Huh? Doo? Boo!\",\n     '65' => \"No! Me dance! / Boo! Kah noh-lah!\",\n     '66' => \"What? What? Whaaaa!!!? / Doo? Doo? Doooooo!!!?\",\n     '67' => \"Wasssssupppp, buddy! / Dooooo Ooooooo-tye, noo-lah!!!!\",\n     '68' => \"Shake, baby! / Koo-bah, bay-bee!\",\n     '69' => \"Me beautiful! / Kah ee-kah!\",\n     '70' => \"Me beautiful diamond! / Kah ee-kah ay-koo!\",\n     '71' => \"Yeah yeah yeah talk! / Wah wah wah noo-noo!\",\n     '72' => \"…happy, yeah yeah, yeah yeah! / Shana noo-loo mah, shana wah wah moo-loo nah!\",\n     '73' => \"Dance time. / Noh-lah toh-toh.\",\n     '74' => \"Dance monster, like like, dance, buddy buddy, buddy, monster, like like, happy / Noh-lah moh-moh tay tay noo-lah noo-lah noo-lah moh-moh tay tay noo-loo.\",\n     '75' => \"OMG! Good sound! / Oh-kah-tee! Ee-day lee-koo!\",\n     '76' => \"Like, blah blah blah! / Tay, blah blah blah!\",\n     '77' => \"Beautiful, baby! / Ee-kah, bay-bee!\",\n     '78' => \"What's up? / Doo-oo-tye?\",\n     '79' => \"Finally. / Oo-tah-toh-toh.\",\n     '80' => \"Finally! Yeah! / Oo-tah-toh-toh! Yeah!\",\n     '81' => \"Funny dance. / Doo-loo noh-lah.\",\n     '82' => \"Good play! / Ee-day loo-lay!\",\n     '83' => \"Play, yeah. / Loo, wah.\",\n     '84' => \"Party time, baby! / Dah-noh-lah, bay-bee!\",\n    #...\n     '86' => \"Good sound! / Ee-day lee-koo!\",\n     '87' => \"More good sound! / Koh-koh ee-day lee-koo!\",\n     '88' => \"Watch me! Watch me! / Ay-ay kah!\",\n     '89' => \"Yay! Whassup? / Yay! Doo-oo-tye?\",\n     '90' => \"Yay! Me love dance!Yay! / Kah may-may noh-lah!\",\n     '91' => \"What's up? You dance! / Doo-oo-tye? Oo-nye noh-lah!\",\n     '92' => \"Dance! Dance! Dance! Dance! / Noh-lah! Noh-lah! Noh-lah! Noh-lah!\",\n     '93' => \"Party time! Yea! / Daaah-nohhhh-lahhhhh! Wah!\",\n     '94' => \"Life is dance! / Tee noh-la!\",\n     '95' => \"Party time! / Dah-noh-lah!\",\n     '96' => \"Me like music! / Kah toh-loo ee-kah-lee-koo!\",\n     '97' => \"You dance! / Oo-nye noh-lah!\",\n     '98' => \"You happy? / Oo-nye noo-loo?\",\n     '99' => \"No! You party monster! / Boo! Oo-nye dah-no-lah moh-moh!\",\n    '100' => \"Funny! / Doo-loo!\",\n    '101' => \"Good joke! / Ee-day loo-loo!\",\n    '102' => \"You Funny! / Oo-nye Doo-loo!\",\n    '102' => \"You funny. / Oo-nye doo-loo.\",\n    '103' => \"No funny! / Boo doo-loo!\",\n    '104' => \"Me monster! / Kah moh moh! Kah moh moh!\",\n    '105' => \"Yay! You me talk.Yay! / Oo-nye kah noo-noo.\",\n    '106' => \"Monster noise / Moh-moh bah-boo\",\n    '107' => \"Dance monster! / Noh-lah moh-moh!\",\n    '108' => \"Talk monster! / Noo-noo moh-moh!\",\n    '109' => \"Yeeehaaa! You big monster! / Yeeehaaa! Oo-nye dah moh-moh!\",\n    '110' => \"You look funny. / Oo-nye ay-ay doo-loo.\",\n    '111' => \"Help! / Ah-noo!\",\n    '112' => \"Finally! Hungry! / Oo-tah-toh-toh! Ay-tay!\",\n    '113' => \"Like, Dance monster! Yeah! / Tay, noh-lah moh-moh! Wah!\",\n    '114' => \"Food. / Ah-tah.\",\n    '115' => \"No pet! / Boo ah-may!\",\n    '116' => \"No like! / Boo toh-loo!\",\n    '117' => \"No touch! / Boo ah!\",\n    '118' => \"Me talk monster! / Kah noo-noo moh-moh!\",\n    '119' => \"Me dance monster! / Kah Noh-lah moh-moh!\",\n    '120' => \"Very good. / Mee-mee ee-day.\",\n    '121' => \"Ok! Ok! Good! Yes! / Oh-kay! Oh-kay! Ee-Day! Ee-tay!\",\n    '122' => \"Life genius! / Tee dah-way!\",\n    '123' => \"Hey! Big no!Hey! / Dah boo!\",\n    '124' => \"Me dizzy! Feed! / Kah ay-way! Ah-tah!\",\n    '125' => \"Oh me genius, love dance, oh me genius… / Oh kah dah-way dee dee doo doo may-may noo-lah oh kah dah-way\",\n    '126' => \"Feed me! / Ah-tah kah!\",\n    '127' => \"Listen, listen, look listen, ooh sound, oh baby… / Ay-ay-lee-koo, ay-ay-lee-koo, ay-ay ay-ay-lee-koo, ooh lee-koo, oh, bay-bee…\",\n    '128' => \"Oooh, you no good, good me genius, genius, genius, genius baby! / Oooh, oo-nye boo ee-day, ee-day kah dah-way, dah-way, dah-way, dah-way bay-bee!\",\n    '129' => \"So funny! / Doh doo-loo!\",\n    '130' => \"Music, baby! / Ee-kah-lee-koo, bay-bee!\",\n    '131' => \"Ahhh Big Genius! / Ahhh Dahhh Dahh-wayyyy!\",\n    '132' => \"Ohhhh myyyy goshhhh! / Ohhhh-kahhhh-teeeee!\",\n    '133' => \"Kiss, baby! / May-tah, bay-bee!\",\n    '134' => \"Me no healthy! / Kah boo koo-doh!\",\n    '135' => \"Like, no way! / Tay, dah-boo!\",\n    '136' => \"Yeah! Dude! / Wah! Deeee-dohhhh!\",\n    #...\n    '138' => \"Whoaaaa! / Whoaaaa!\",\n    '139' => \"Oy! Huh? / Oy! Doo?\",\n    '140' => \"Me big dance! / Kah dah noh-lah!\",\n    '141' => \"Little dance! / Dee noh-lah!\",\n    '142' => \"Watch me dance! / Ay-ay kah noh-lah!\",\n    '143' => \"Dance! Yeah! / Noooh-Laaah! Wah!\",\n    '144' => \"Me tired. More food! / Kah way-loh. Koh-koh ah-tah!\",\n    '145' => \" Look good me, good sound, good sound, good sound… / Ay-ay ee-day kah, ee-day lee-koo, ee-day lee-koo, ee-day lee-koo…\",\n    '146' => \"No like game! / Boo toh-loo doo-ay-loo-lah!\",\n    '147' => \"You pull! / Oo-nye ah-loo!\",\n    '148' => \"Yes! Uh-huh! / Ee-tay! Uh-huh!\",\n    '149' => \"Like, OK! / Tay, oh-kay!\",\n    '150' => \"Like, really? REALLY??? / Tay, doo? DOO???\",\n    '151' => \"Ahhh Big Genius! / Ahhh Dahhh Dahh-wayyyy!\",\n    '152' => \"Yes! Yes! Yes! / Ee-tay! Ee-tay! Ee-tay!\",\n    '153' => \"Ha! Dude! / Ha! Dee-doh!\",\n    '154' => \"Thank you. / Dah-kah-oo-nye.\",\n    #...\n    '157' => \"Life good upside down. / Tee ee-day oo-tye nah-bah.\",\n    '158' => \"Me happy. / Kah noo-loo.\",\n    '159' => \"Life good! / Tee ee-day!\",\n    '160' => \"Dude! Dude good friend. / Dee-doh! Dee-doh ee-day noo-lah.\",\n    '161' => \"Ha! Dude! / Ha! Dee-doh!\",\n    '162' => \"Love you, dude! / May-may oo-nye, dee-doh!\",\n    '163' => \"Genius talk! / Dah-way noo-noo!\",\n    '164' => \"Me like friend. / Kah toh-loo noo-lah.\",\n    '165' => \"Me tough! / Kah tay-boo-koo!\",\n    '166' => \"Me happy dude. / Kah noo-loo dee-doh.\",\n    '167' => \"No fun. / Boo doo-ay.\",\n    '168' => \"Very very very very fun! / Mee-mee mee-mee mee-mee mee-mee doo-ay!\",\n    '169' => \"Me big dude. / Kah dah dee-doh.\",\n    '170' => \"More joke. / Koh-koh loo-loo.\",\n    '171' => \"Tickle! / Nee-tye!\",\n    '172' => \"Huh? You eat me? / Doo? oo-nye ay-tay kah?\",\n    '173' => \"Okay, uncle... / Oh-kay, oo-kah...\",\n    '174' => \"Play Play Play Play Play! / LooLooLooLooLooooo!\",\n    '175' => \"Ticke me! Now! / Nee-tye kah! Nee-way!\",\n    '176' => \"Oh, very very! / Ohhh, mee-mee mee-mee!\",\n    #...\n    '178' => \"Whassssssupppppp! / Dooooo-ooooooo-tye!\",\n    '179' => \"Me dizzy. / Kah ay-way.\",\n    '180' => \"No dance. / Boo noh-lah.\",\n    '181' => \"Dance? Really? / Noh-lah? Doo?\",\n    '182' => \"Sweet! Uh-huh! Party! / Nee-may! Uh-huh! Dah-no-lah!\",\n    #...\n    '185' => \"More! More! / Koh-koh! Koh-koh!\",\n    #...\n    '187' => \"Me like tickle! / Kah toh-loo nee-tye!\",\n    #...\n    '189' => \"Help! Me scared... NOT! / Ah-noo! Me dah-boh-bay...KAH-LOO-LOO!\",\n    '190' => \"Dude. What's up? / Dee-doh. Doo-oo-tye?\",\n    '191' => \"Monster! Help! / Moh-moh! Ah-noo!\",\n    '192' => \"You think me funny? / Oo-nye way kah doo-loo?\",\n    #...\n    '195' => \"Please. / Doo-moh.\",\n    '196' => \"Dizzy! / Ay-way!\",\n    '197' => \"Me dizzy. / Kah ay-way.\",\n    '198' => \"Dizzy! Dizzy! / Ay-way! Ay-way!\",\n    '199' => \"Me no hungry. / Kah boo ay-tay.\",\n    #...\n    '202' => \"More play! / Koh-koh loo-lay!\",\n    #...\n    '204' => \"Uh-oh. / Uh-oh.\",\n    #...\n    '206' => \"Like, when talk? / Tay, doo noo-noo?\",\n    #...\n    '208' => \"Yeah? / Doo?\",\n    '209' => \"Yes! Uh-huh! / Ee-tay! Uh-huh!\",\n    '210' => \"Yeah! Hug time! / Wah! May-lah toh-toh!\",\n    #...\n    '212' => \"When talk? / Doo noo-noo?\",\n    '213' => \"Hmm? Like, why hug? / Doo? Tay, doo may-lah?\",\n    '214' => \"Oh yeah! / Ohhhh wah!\",\n    '215' => \"No talk, no love! / Boo noo-noo, boo may-may!\",\n    #...\n    '217' => \"Like, me dizzy. / Tay, kah ay-way.\",\n    '218' => \"You no good! / Oo-nye boo ee-day!\",\n    '219' => \"You no listen. / Oo-nye boo ay-ay-lee-koo.\",\n    '220' => \"Where friend? / Doo noo-lah?\",\n    '221' => \"Love it! / Dah-may-may!\",\n    #...\n    '223' => \"Oh! / Oh!\",\n    '224' => \"Oh! Me like! Yeah! Yeah! Yeah!Oh! / Kah toh-loo! Wah! Wah! Wah!\",\n    '225' => \"Yeah! Yeah! Yeaaaahhh! / Wah! Wah! Waaaaaaahhhh!\",\n    #...\n    '228' => \"Boring… / Boo-doo-ay…\",\n    #...\n    '238' => \"Dizzy sounds! / Ay-way lee-koo!\",\n    '239' => \"Noisy song. / Bah-boo wah-tee.\",\n    #...\n    '242' => \"Like, you like song? / Tay, oo-nye toh-loo wah-tee?\",\n    '243' => \"No more noise! / Boo koh-koh bah-boo!\",\n    '244' => \"Huh? Oh! You play! / Doo? Oh! Oo-nye loo-lay!\",\n    '245' => \"Why joke? / Doo loo-loo?\",\n    '246' => \"Play game? / Loo doo-ay-loo-lah?\",\n    '247' => \"Hey! You funny? / Hey! Oo-nye doo-loo?\",\n    '248' => \"Hey! No more joke. / Hey! Boo koh-koh loo-loo.\",\n    '249' => \"Love tickle! / May-may nee-tye!\",\n    '250' => \"Sweet shake, baby! / Nee-may koo-bah, bay-bee!\",\n    '251' => \"You move me, uh-huh! / Oo-nye noh-bah kah, uh-huh!\",\n    #...\n    '255' => \"Me see! / Kah ay-ay!\",\n    '256' => \"No way! / Daahhhh-boooo!\",\n    #...\n    '258' => \"No way! Like, get out! / Dah-boo! Tay, bye-bye!\",\n    #...\n    '261' => \"Like, me like! Like, to say like! Like! / Tay, kah toh-loo! Tay, noo-noo tay! Tay!\",\n    '262' => \"Like, you little talk! Me big talk! / Tay, oo-nye dee noo-noo! Kah dah noo-noo!\",\n    '263' => \"Talk talk talk talk! / Noo-noo noo-noo noo-noo noo-noo!\",\n    '264' => \"Talk more! / Noo-noo koh-koh!\",\n    '265' => \"Talk more! Talk more! / Noo-noo koh-koh! Noo-noo koh-koh!\",\n    '266' => \"You talk pretty, like love song! / Oo-nye noo-noo ee-kah, tay may-may wah-tee!\",\n    '267' => \"Like, friends love talking. / Tay, noo-lah may-may noo-noo.\",\n    '268' => \"Like, good hug. / Tay, ee-day may-lah.\",\n    '269' => \"Like, holding and talking! / Tay, ah koh noo-noo!\",\n    '270' => \"OMG! / Oh-kah-tee!\",\n    '271' => \"Like, yes! / Tay, ee-tay!\",\n    #...\n    '273' => \"Talking is life. / Noo-noo tee.\",\n    '274' => \"You genius! / Oo-nye dah-way!\",\n    '275' => \"Like, whooaaa! / Tay, whooaaa!\",\n    #...\n    '277' => \"No be worried, baby! / Boo boh boh-bay, bay-bee!\",\n    '278' => \"Huh? Me dreaming? / Doo? Kah way-loo?\",\n    '279' => \"Dude! You dreaming! / Dee-doh! Oo-nye way-loo!\",\n    '280' => \"Rock shake! / Boo-koo koo-bah!\",\n    '281' => \"Uh-huh! Uh-huh! Rock! / Uh-huh! Uh-huh! Boo-koo!\",\n    '282' => \"You rock! / Oo-nye boo-koo un-go-boh!\",\n    '283' => \"Me rock! Whooo! / Kah boo-koo! Whooo!\",\n    '284' => \"Me rock monster! / Kah boo-koo moh-moh!\",\n    '285' => \"Oy oy oy, monster! / Oy oy oy, moh-moh!\",\n    '286' => \"Ayyy! Joke monster! / Ayyy! Loo-loo moh-moh!\",\n    '287' => \"Genius dance! Whooo! / Dah-way noh-lah! Whooo!\",\n    '288' => \"Hug! Hug! / May-lah! May-lah!\",\n    '289' => \"Funny sound! / Doo-loo lee-koo!\",\n    '290' => \"Funny noise! / Doo-loo bah-boo!\",\n    '291' => \"Oh yes! / Oooohh, ee-tay!\",\n    '291' => \"Oh yes! / Oh ee-tay!\",\n    '292' => \"Say what? / Noo-noo doo?\",\n    '293' => \"Say no more. / Noo-noo boo koh-koh.\",\n    '294' => \"Hungry! Hungry! Help... / Ay-tay! Ay-tay! Ah-noo.\",\n    '295' => \"Song? / Wah-tee?\",\n    '296' => \"Yeah, baby! / Wah, bay-bee!\",\n    '297' => \"Seriously? / Loo-loo-doo?\",\n    '298' => \"Yeah baby! Yeah baby! / Wah bay-bee! Wah bay-bee!\",\n    '299' => \"Say more / Noo-noo koh-koh\",\n    '300' => \"Yay! / Yay!\",\n    #...\n    '302' => \"What? Say more. / Doo? Noo-noo koh-koh.\",\n    '303' => \"Me hear you! / Kah ay-ay-lee-koo oo-nye!\",\n    '304' => \"Baby! Yes! / Bay-bee! Ee-tay!\",\n    '305' => \"Baby! / Bay-bee!\",\n    '306' => \"Again please. / Koh-koh doo-moh.\",\n    '307' => \"Happy! / Noo-loo!\",\n    '308' => \"Hey, me very happy, yeah! / Hey, kah mee-mee noo-loo, wah!\",\n    '309' => \"Love you! Whoooo! / May-may oo-nye! Whoooo!\",\n    '310' => \"No happy. / Boo noo-loo.\",\n    '311' => \"Me see you. / Kay ay-ay oo-nye.\",\n    '312' => \"No feel good. / Boo koo-doh e-day.\",\n    '313' => \"Please, hungry! Please, hungry! / Doo-moh, ay-tay!\",\n    '314' => \"Me sleep. / Kah way-loh.\",\n    '315' => \"What? Tell me more. / Doo? Wee-tah kah koh-koh.\",\n    '316' => \"Pet more!Mmmmm! / Ah-may koh-koh!\",\n    '317' => \"Not bad. / Boo boo-dah.\",\n    '318' => \"Uh-uh. Pet bad. / Uh-uh. Ah-may boo-dah.\",\n    '319' => \"Yay! Me like! / Yay! Kah toh-loo!\",\n    '320' => \"Uh-uh. / Uh-uh.\",\n    '321' => \"Okay, you love me. / Oh-kay, oo-nye may-may kah.\",\n    '322' => \"Me love you! / Kah may-may oo-nye!\",\n    '323' => \"Uh-huh! Uh-huh! Uh-huh! / Uh-huh! Uh-huh! Uh-huh!\",\n    '324' => \"Party! / Dah-noh-lah!\",\n    '325' => \"Oh baby! / Oh bay-bee!\",\n    '326' => \"Good joke. / Ee-day loo-loo.\",\n    '327' => \"No time! / Boo toh-toh!\",\n    '328' => \"You me talk. / Oo-nye kah noo-noo.\",\n    '329' => \"Uh-huh! / Uh-huh!\",\n    '330' => \"Okay. / Oh-kay.\",\n    '331' => \"Uh-uh. / Uh-huh.\",\n    '332' => \"No! No! / Dah-boo! Dah-boo!\",\n    '333' => \"Tell me more… / Noo-noo koh-koh!\",\n    '334' => \"Me hungry. / Kah ay-tay.\",\n    '335' => \"Me big dance. / Kah dah noh-lah.\",\n    '336' => \"Me like noise! / Kah toh-loo bah-boo.\",\n    '337' => \"Me like! / Kah toh-loo!\",\n    '338' => \"You big talk. / Oo-nye dah noo-noo.\",\n    '339' => \"Over, over, dizzy, me change! / Oo-bah, o-bah, ay-way, kah boo-tay!\",\n    '340' => \"Hey! No touch! / Hey! Boo ah!\",\n    '341' => \"More! More! More! More! / Koh-koh! Koh-koh! Koh-koh! Koh-koh!\",\n    '342' => \"Again! More! / Koh-koh! Koh!\",\n    '343' => \"You like me funny? / Oo-nye toh-loo kah doo-loo?\",\n    '344' => \"Dude. Not funny. / Dee-doh. Boo doo-loo.\",\n    '345' => \"Me dizzy! / Kah ay-way!\",\n    '346' => \"Thank you, thank...you... / Dah-kah-oo-nye, dah-kah...oo-nye...\",\n    '347' => \"No me hungry. / No kah ay-tay.\",\n    '348' => \"Yea! Yea! / Wah! Wah!\",\n    '349' => \"You me dance. / Oo-nye kah noh-lah.\",\n    #...\n    '650' => \"Uh-huh! / Uh-huh!\",\n    '651' => \"That's right! / Boh-ee-tay!\",\n    '652' => \"Yeah yeah yeah! Very good! / Wah wah wah! Mee-mee ee-day!\",\n    '653' => \"Hmm? Pet time? / Doo? Ah-may toh-toh?\",\n    '654' => \"Huh? Okay! / Doo? Oh-kay!\",\n    '655' => \"Sound time? / Lee-koo toh-toh?\",\n    '656' => \"Like, you funny! / Tay, oo-nye doo-loo!\",\n    '657' => \"Yeah! Like, mmm-kay! / Wah! Tay, mmm-kay!\",\n    '658' => \"What? Really? Ohhhh! / Doo? Loo-loo-doo? Ohhhh!\",\n    '659' => \"Ohhh yeah! You go, buddy! / Ohhh wah! Oo-nye bye-bye, noo-lah!\",\n    '660' => \"Oh, that's right! You genius, friend! / Oh, boh-ee-tay! Oo-nye dah-way, noo-lah!\",\n    '661' => \"OMG! What? / Oh kah-teee! Doo?\",\n    '662' => \"OMG! What? Really? / Oh kah-tee! Doo? Loo-loo-doo?\",\n    '663' => \"Like, finally! / Tay, oo-tah-toh-toh!\",\n    '664' => \"Yeah! Yeah! Yeah! Like, yippee! / Wah! Wah! Wah! Tay, yippee!\",\n    '665' => \"Uh-huh! Talk, more talk, yeah! / Uh-huh! Noo-noo, koh-koh noo-noo, wah!\",\n    '666' => \"OMG! Like, oh, my, gosh! / Oh kah-tee! Tay, oh, kah, tee.\",\n    '667' => \"Like, me sad. / Tay, kah boo-noo-loo.\",\n    '668' => \"Like, you look funny. / Tay, oo-nye ay-ay doo-loo.\",\n    '669' => \"Like, path high. / Tay, bye-way oo.\",\n    '670' => \"Like, me hungry! / Tay, kah ay-tay!\",\n    '671' => \"No! No! No! Really? / Boo! Boo! Boo! Dooo?\",\n    '672' => \"Like, over, over! Over, over! Over,over! More talk, more talk, more talk, more talk! OMG! / Tay, oo-bah, oo-bah! Ooh-bah, oo-bah! Koh noo-noo, koh noo-noo, koh noo-noo, koh noo-noo, OH KAH TEE!\",\n    '673' => \"Yay! Talk Time! / Yay! Noo-noo toh-toh!\",\n    '674' => \"Hey! / Hey!\",\n    '675' => \"Yeah! / Wah!\",\n    '676' => \"Really? Oh! / Loo-loo-doo? Oh!\",\n    '677' => \"OK! / Oh-kay!\",\n    '678' => \"Oh yeah! Uh-huh! / Oh wah! Uh-huh!\",\n    '679' => \"Oh yeah! Oh yeah! / Oh wah! Oh wah!\",\n    '680' => \"Bye-bye, bye-bye! / Bye-bye, bye-bye!\",\n    '681' => \"Yeah! Yeah! / Wah! Wah!\",\n    '682' => \"OK! / Oh-kay!\",\n    '683' => \"Yeah! Yeah! Yeah! / Wah! Wah! Wah!\",\n    '684' => \"What's up? / Doo-oo-tye?\",\n    '685' => \"Huh? Hey! / Doo? Hey!\",\n    '686' => \"Huh? Really? / Doo? Loo-loo-doo?\",\n    '687' => \"Whoohoo! Uh-huh! Oh yeah / Uh-huh! Oh wah!\",\n    '688' => \"Hey, hey, hey! / Hey, hey, hey!\",\n    '689' => \"Uh-huh! Yeah! Like, mmm-kay! / Uh-huh! Wah! Tay, mmm-kay!\",\n    '690' => \"No way?! / Dah-boo!?!\",\n    '691' => \"OK! Yeah! / Oh-kay! Wah!\",\n    '694' => \"Oh. me see! / Oh. Kah ay-ay!\",\n    #...\n    '697' => \"Really? / Doooo?\",\n};\n\nsub description {\n    return $dictionary->{shift};\n}\n\n1;"
  },
  {
    "path": "lib/Furby/Command.pm",
    "content": "# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\npackage Furby::Command;\n\nuse Furby::Command::Dictionary;\n\n# Commands marked with \"!\" are available in the official iOS Furby app (i.e. they are supposed to work one way or another)\n# The potential range for commands is [0..1023]. Some of the command codes missing from this list are just used as\n# Furby responses (see Furby::Command::Dictionary)\n\n# When the command is understood (e.g. the food was accepted), Furby will respond with his current personality type [900..905].\n\nmy $description = {\n    #...\n    '350' => '', #! food, tasty (\"mmm, yum\")\n    #...\n    '352' => 'Any small eatable tasty stuff (like peanut)', #!\n    '353' => 'Any bigger soft eatable tasty stuff (like banana)', #!\n    '354' => 'Any suckable tasty stuff (like oysters, sphagetti)', #!\n    '355' => 'Any drinkable(?) tasty stuff', #!\n    '356' => 'Any hard eatable but not tasty stuff (like chicken bone)', #! \n    #...\n    '358' => 'Any small not tasty stuff (like pepperoni)', #!\n    '359' => 'Any bigger soft not tasty stuff (like asparagus)', #!\n    '360' => 'Any suckable not tasty stuff ', #!\n    '361' => '', #!\n    #...\n    '368' => '', #!\n    #...\n    '370' => '', #!\n    '371' => '', #!\n    '372' => 'Any suckable tasty stuff (like beans)', #! \"ooh!\"\n    '373' => '', #!\n    '374' => '', #!\n    #...\n    '376' => '', #!\n    '377' => '', #!\n    '378' => '', #!\n    '379' => '', #!\n    '380' => '', #!\n    #...\n    '382' => '', #!\n    '383' => '', #!\n    '384' => '', #!\n    '385' => '', #!\n    '386' => '', #!\n    #...\n    '388' => '', #!\n    '389' => '', #!\n    '390' => '', #!\n    '391' => '', #!\n    '392' => '', #!\n    #...\n    '394' => '', #!\n    '395' => '', #!\n    '396' => '', #!\n    '397' => '', #!\n    '398' => '', #!\n    #...\n    '400' => '', #!\n    '401' => '', #!\n    '402' => '', #!\n    '403' => '', #!\n    #...\n    '410' => '', #! something hot?\n    #...\n    '412' => '', #! something hot?\n    '413' => '', #! something hot?\n    '414' => '', #!\n    '415' => '', #!\n    '416' => '', #!\n    '417' => 'Any non-eatable stuff (toilet paper, pillow, etc)', #!\n    '418' => '', #!\n    '419' => '', #!\n    '420' => '', #!\n    '421' => '', #!\n    '422' => '', #!\n    '423' => '', #!\n    '424' => '', #!\n    '425' => '', #!\n    #...\n    '700' => 'Event: I\\'m bored / sleepy', # does various silly things depending on personality\n    '701' => 'Event: Burp',\n    '702' => 'Event: Chew-chew',\n    '703' => 'Event: You touched my head or side, you turned me on a side',\n    '704' => 'Event: Fart',\n    '705' => 'Event: I woke up!', # sent on Furby wakeup, even before it pronounces the 'Good morning' phrase\n    '706' => 'Event: (?)', # sent at random when Furby is idle (like ping, or searching for other Furbys?). Responds with '721'\n    '707' => 'Event: (?)', # sent at random when Furby is idle (like ping, or searching for other Furbys?). Responds with '722'\n    '708' => 'Event: (?)', #  speech: dang-dang-dang-da..... Responds with '723'\n    '709' => 'Event: (?)', #  speech: bo-ga-di-di-do ... dang-dang-dang-da..... Responds with '724'\n    '710' => 'Event: Me happy (also when head or back is touched)',\n    '711' => 'Event: cough-cough-cough',\n    '712' => 'Event: Me hungry! / Kah Ay-tay!',\n    '713' => 'Event: You touched my tummy; also sent on its own like <touch my tummy, please?> / As command: imitate tummy touch(?)', #<\n    '714' => '', #  something that he dislikes (vomit like sound, changes eyes)\n    '715' => '', #  something he dislikes? speaks rapidly\n    '716' => 'Event: Me happy (you touched my side or back, or head)',\n    '717' => 'Event: Achoo!',\n    '718' => 'Event: Yawn (I\\'m going to sleep) / As command: Yawn!', # when sent as a command, will yawn / sent automatically twice when Furby is going into deep sleep mode\n    '719' => 'Event: Whisper, whisper, he-he-he',\n    '720' => '', #  something he dislikes, \"uh oh kaa tee, do?\"\n    '721' => 'Event: got a command 706 (plus sings a song); as command: sing that song', #! handshake?\n    '722' => 'Event: got a command 707 (plus sings a song); as command: sing that song', #! handshake?\n    '723' => 'Event: got a command 708 (plus sings a song); as command: sing that song', #! handshake?\n    '724' => 'Event: got a command 709 (plus sings a song); as command: sing that song', #! handshake?\n    #...\n    '760' => '', #  \"love friend, nay nay noo la\"\n    '761' => '', #  \"day-dee\"\n    '762' => '', #  \"meila koo mei ta, meila koo mei ta, like mua, mua, mua\" (kiss)\n    '763' => '', #  \"ka tulu ata, ata, ata, ata\" (song)\n    '764' => '', #  \"witi wati to to, blah blah blah blah blah, blah\" (song)\n    '765' => '', #  \"(fart) oh-ho-ho, tu lu li ku!\"\n    '766' => '', #  \"boda tei ta eу ku, shaa!\"\n    #...\n    '780' => 'Event: I\\'ve got command 790',\n    '781' => 'Event: I\\'ve got command 791',\n    #...\n    '790' => '', #  \"ee day do lay lo la!\". Responds with '780'\n    '791' => '', #  \"u nai bo li day\". Responds with '781'\n    #...\n    '813' => 'Request: What\\'s your personality?', #! used to initate handshake step 1 and get the current personality type\n    #...\n    '820' => 'Hypnotize for 1 minute', #! this command is sent by iOS app immediately on application start and every 40 seconds. Responds with current character type (90x).\n    #...\n    '830' => '', #!\n    '831' => '', #\n    '832' => '', #!\n    #...\n    '850' => '', #  \"yeah mi mi be day mu ha ha\", \"yeah, yeah, yeah! oh yeah yeah!\"\n    '851' => '', #  \"mmm, ka tulu, me like\", \"haa\"\n    '852' => '', #  \"oh-oh-oh-oh!\", \"dude, no!\"\n    '853' => '', #  \"grrrh ko ko\" something he dislikes\n    '854' => '', #  \"grrrh ko ko\", \"ka bu ku do\"\n    '855' => '', #  \"ka happy dude\", \"a ha pee pee day\", \"yeah nice good boo tai naba\"\n    '856' => '', #  \"u-ho-ho, a-ho-ho\", \"blah blah blah blah\"\n    '857' => '', #  \"a ha! bu da to to\", \"u ha, he-he-he\"\n    '858' => '', #  \"ha? du?\"\n    '859' => '', #  \"a ha\", \"oh, kah hey hey, I see\"\n    '860' => '', #  \"ahaa.. be day loo ney\", \"haa\", \"um, mmm, hm\"\n    '861' => '', #  \"huh\" (bored?)\n    '862' => 'Command: Sleep!', #! sleep (for several seconds)\n    '863' => 'Command: Laugh!', #! laugh\n    '864' => 'Command: Burp!', #! burp\n    '865' => 'Command: Fart!', #! fart/poo\n    '866' => 'Command: Purr!', #! purr\n    '867' => 'Command: Sneeze!', #! long sneeze\n    '868' => 'Command: Sing!', #! sing\n    '869' => 'Command: Talk!', #! talk\n    '870' => '', #  \"me tay, yes, yes!\" (hearts in his eyes), \"di do dude, good friend\"\n    '871' => '', #  \"ah bu to lu, no like\"\n    '872' => '', #  says some rap\n    '873' => '', #  sings: \"di-di-di-di, tam-da-di dam\"\n    '874' => '', #  eating motion\n    '875' => '', #  angry bite, \"he-he-he\"\n    '876' => '', #  chewing/sucking sound (like when someone puts a finger in his mouth)\n    '877' => '', #  suck in\n    '878' => '', #  ingest\n    '879' => '', #  short sneeze, \"like pea\" (or something else, depending on personality)\n    '880' => '', #! motion (dislike)\n    '881' => '', # \"oh-hoo ha-ha\" (or \"ha-haa\" like something scary or hot)\n    '882' => '', #! \"ah ha me bee dey, nice good!\", purr\n    '883' => '', #  tasty: \"mmm, yum\", \"ha haa!\"\n    '884' => '', #  not tasty (vomit-like)\n    '885' => '', #  \"oooh\"\n    '886' => '', #  \"chew chew ooh\"\n    '887' => '', #! \"aaah\" (exclamation) (changes eyes to 'burning' in warrior mode)\n    '888' => '', #  \"aaah\"\n    '889' => '', #! motion (sings \"ahh-tahoo\")\n    '890' => '', #  \"baby\"\n    '891' => '', #  \"ka ay tay oooh\", \"purr grrr uh oh\"\n    '892' => '', #  \"mmm, good food\"\n    '893' => '', #\n    #...\n    '900' => 'I have no personality developed yet...',\n    '901' => 'I\\'m a princess!',\n    '902' => 'I\\'m a diva!',\n    '903' => 'I\\'m a warrior!',\n    '904' => 'I\\'m a joker!',\n    '905' => 'I\\'m a gossip queen!',\n    '906' => 'My personality is #906 [SNUGGLEBY]',\n    '907' => 'My personality is #907 [SASSBY]',\n    '908' => 'My personality is #908 [SCOFFBY]',\n    '909' => 'My personality is #909 [CHUCKLEBY]',\n    '910' => 'My personality is #910 [GASSBY]',\n    '911' => 'My personality is #911 [LATEBY]',\n};\n\nsub description {\n    my $command = shift;\n    return $description->{$command} || Furby::Command::Dictionary::description($command);\n}\n\n1;"
  },
  {
    "path": "lib/Furby/Packet.pm",
    "content": "# Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\n\npackage Furby::Packet;\n\nuse strict;\n\nmy @checksums = (\n    # First packet (higher 5 bits of the command number)\n    '0000', #  0\n    '0110', #  1\n    '0210', #  2\n    '0300', #  3\n    '1023', #  4\n    '1133', #  5\n    '1233', #  6\n    '1323', #  7\n    '0120', #  8\n    '0201', #  9\n    '0330', # 10\n    '1021', # 11\n    '1103', # 12\n    '1222', # 13\n    '1313', # 14\n    '2021', # 15\n    '0220', # 16\n    '0330', # 17\n    '1000', # 18\n    '1110', # 19\n    '1203', # 20\n    '1313', # 21\n    '2000', # 22\n    '2110', # 23\n    '0300', # 24\n    '1011', # 25\n    '1120', # 26\n    '1201', # 27\n    '1323', # 28\n    '2011', # 29\n    '2120', # 30\n    '2201', # 31\n\n    # Second packet (lower 5 bits of the command number + 32)\n    '1033', #  0 + 32\n    '1123', #  1 + 32\n    '1223', #  2 + 32\n    '1333', #  3 + 32\n    '2033', #  4 + 32\n    '2123', #  5 + 32\n    '2223', #  6 + 32\n    '2333', #  7 + 32\n    '1113', #  8 + 32\n    '1232', #  9 + 32\n    '1303', # 10 + 32\n    '2031', # 11 + 32\n    '2113', # 12 + 32\n    '2232', # 13 + 32\n    '2303', # 14 + 32\n    '3012', # 15 + 32\n    '1213', # 16 + 32\n    '1303', # 17 + 32\n    '2010', # 18 + 32\n    '2100', # 19 + 32\n    '2213', # 20 + 32\n    '2303', # 21 + 32\n    '3033', # 22 + 32\n    '3123', # 23 + 32\n    '1333', # 24 + 32\n    '2001', # 25 + 32\n    '2130', # 26 + 32\n    '2211', # 27 + 32\n    '2333', # 28 + 32\n    '3022', # 29 + 32\n    '3113', # 30 + 32\n    '3232', # 31 + 32\n);\n\nsub make {\n    my ($command) = @_;\n\n    die \"Command number must be in [0..1023] range\" if ($command < 0 or $command > 1023);\n\n    my $packet1 = $command >> 5;        # high 5 bits (paddded to 6 bits)\n    my $packet2 = ($command & 31) + 32; # low 5 bits (with 6th bit set to 1)\n\n    my $s1 = bin2quad('11'.dec2bin($packet1)).'-'.$checksums[$packet1].'-1032';\n    my $s2 = bin2quad('11'.dec2bin($packet2)).'-'.$checksums[$packet2].'-1032';\n\n    return \"$s1 $s2\";\n}\n\nsub parse {\n    my ($sequence) = @_;\n    $sequence =~ s/[^0123]//g; # leave just digits\n    $sequence =~ s/1032$//; # remove identifier at the end, if any\n    die \"Sequence should contain 8 or 12 quaternary digits\" unless $sequence =~ m/^([0123]{4})([0123]{4})$/;\n\n    my $byte = quad2dec($1);\n    return -1 unless ($byte & 192) == 192; # two higer bits must be set\n    #print \"{$byte}\";\n\n    $byte &= 63; # use 6 leftmost bits\n    my $checksum = $checksums[$byte];\n    return -1 unless ($checksum == $2); # checksums must match\n\n    #print \"<$1><$2>=<$byte><$checksum>\\n\";\n\n    return $byte;\n}\n\nsub quad2dec {\n    my $s = shift;\n    my $out = 0;\n    foreach my $digit (split(//, $s)) {\n        $out = ($out << 2) | $digit;\n    }\n    return $out;\n}\n\nsub bin2quad {\n    my $s = shift;\n    my $out;\n    foreach my $pair (split(/(\\d\\d)/, $s)) {\n        $out .= bin2dec($pair) if $pair ne '';\n    }\n    return $out;\n}\n\nsub dec2bin {\n    return sprintf('%06b', shift);\n}\n\nsub bin2dec {\n    return unpack(\"N\", pack(\"B32\", substr(\"0\" x 32 . shift, -32)));\n}\n\n1;"
  },
  {
    "path": "src/.gitignore",
    "content": "*.dproj\r\n*.identcache"
  },
  {
    "path": "src/dsplay/README.md",
    "content": "dsplay.exe is a simple console utility to play WAV files using DirectSound. The only supported format is 44100Hz 16bit mono PCM.\r\nThe reason behind using this utility is that the playback of high-pitch sound commands via DirectSound\r\nis much smoother (no audible clicks can be heared) than via using any other old-fashioned Windows audio APIs.\r\n\r\nUsage: dsplay.exe filename.wav\r\n"
  },
  {
    "path": "src/dsplay/dsplay.dpr",
    "content": "// Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\r\n\r\nprogram dsplay;\r\n\r\n// Uses DirectX 9 units from\r\n// http://clootie.ru/delphi/download_dx92.html\r\n\r\n{$APPTYPE CONSOLE}\r\n\r\nuses\r\n  Classes,\r\n  DirectSound,\r\n  MMSystem,\r\n  SysUtils,\r\n  Windows;\r\n\r\nconst\r\n  SLEEP_EXTRA_TIME_BEFORE_CLOSE = 40; // ms\r\n\r\nvar\r\n  FileName: String;\r\n  DirectSound: IDirectSound = nil;\r\n  PrimarySoundBuffer: IDirectSoundBuffer = nil;\r\n  SecondarySoundBuffer: IDirectSoundBuffer = nil;\r\n  Status: DWord;\r\n\r\nprocedure WriteDataToBuffer(\r\n  Buffer: IDirectSoundBuffer; OffSet: DWord; var SoundData;\r\n  SoundBytes: DWord);\r\nconst\r\n  LockErrorMessage = 'DirectSoundBuffer.Lock failed';\r\nvar\r\n  AudioPtr1, AudioPtr2: Pointer;\r\n  AudioBytes1, AudioBytes2: DWord;\r\n  h: HResult;\r\n  Temp: Pointer;\r\nbegin\r\n  H := Buffer.Lock(OffSet, SoundBytes, @AudioPtr1, @AudioBytes1, @AudioPtr2, @AudioBytes2, 0(*DSBLOCK_ENTIREBUFFER*));\r\n\r\n  if H = DSERR_BUFFERLOST then\r\n  begin\r\n    Buffer.Restore;\r\n    if Buffer.Lock(OffSet, SoundBytes, @AudioPtr1, @AudioBytes1, @AudioPtr2, @AudioBytes2, 0) <> DS_OK then\r\n      raise Exception.Create(LockErrorMessage);\r\n  end else\r\n  if H <> DS_OK then\r\n    raise Exception.Create(LockErrorMessage);\r\n\r\n  Temp := @SoundData;\r\n  Move(Temp^, AudioPtr1^, AudioBytes1);\r\n\r\n  if AudioPtr2 <> nil then\r\n  begin\r\n    Temp := @SoundData; Inc(Integer(Temp), AudioBytes1);\r\n    Move(Temp^, AudioPtr2^, AudioBytes2);\r\n  end;\r\n\r\n  if Buffer.UnLock(AudioPtr1, AudioBytes1, AudioPtr2, AudioBytes2) <> DS_OK then\r\n    raise Exception.Create(LockErrorMessage);\r\nend;\r\n\r\nprocedure CreateWritePrimaryBuffer;\r\nvar\r\n  Handle: THandle;\r\n  BufferDesc: DSBUFFERDESC;\r\n  PCM: TWaveFormatEx;\r\nbegin\r\n  FillChar(BufferDesc, SizeOf(DSBUFFERDESC), 0);\r\n  FillChar(PCM, SizeOf(TWaveFormatEx), 0);\r\n  with BufferDesc do\r\n  begin\r\n    PCM.wFormatTag := WAVE_FORMAT_PCM;\r\n    PCM.nChannels := 2;\r\n    PCM.nSamplesPerSec := 44100;\r\n    PCM.nBlockAlign := 4;\r\n    PCM.nAvgBytesPerSec := PCM.nSamplesPerSec * PCM.nBlockAlign;\r\n    PCM.wBitsPerSample := 16;\r\n    PCM.cbSize := 0;\r\n    dwSize := SizeOf(DSBUFFERDESC);\r\n    dwFlags := DSBCAPS_PRIMARYBUFFER;\r\n    dwBufferBytes := 0;\r\n    lpwfxFormat := nil;\r\n  end;\r\n\r\n  Handle := GetForegroundWindow();\r\n  if (Handle = 0) then\r\n    Handle := GetDesktopWindow();\r\n\r\n  if DirectSound.SetCooperativeLevel(Handle, DSSCL_WRITEPRIMARY) <> DS_OK then\r\n    raise Exception.Create('DirectSound.SetCooperativeLevel(DSSCL_WRITEPRIMARY) failed');\r\n\r\n  if DirectSound.CreateSoundBuffer(BufferDesc, PrimarySoundBuffer, nil) <> DS_OK then\r\n    raise Exception.Create('DirectSound.CreateSoundBuffer failed');\r\n\r\n  if PrimarySoundBuffer.SetFormat(Addr(PCM)) <> DS_OK then\r\n    raise Exception.Create('PrimarySoundBuffer.SetFormat failed');\r\n\r\n  if DirectSound.SetCooperativeLevel(Handle, DSSCL_NORMAL) <> DS_OK then\r\n    raise Exception.Create('DirectSound.SetCooperativeLevel(DSSCL_NORMAL) failed');\r\nend;\r\n\r\nprocedure CreateWriteSecondaryBuffer(\r\n  var Buffer: IDirectSoundBuffer; SamplesPerSec: Integer;\r\n  Bits: Word; isStereo: Boolean; SizeInBytes: Integer);\r\n\r\nvar\r\n  BufferDesc: DSBUFFERDESC;\r\n  PCM: TWaveFormatEx;\r\nbegin\r\n  FillChar(BufferDesc, SizeOf(DSBUFFERDESC), 0);\r\n  FillChar(PCM, SizeOf(TWaveFormatEx), 0);\r\n\r\n  with BufferDesc do\r\n  begin\r\n    PCM.wFormatTag := WAVE_FORMAT_PCM;\r\n\r\n    if isStereo then\r\n      PCM.nChannels := 2\r\n    else\r\n      PCM.nChannels := 1;\r\n\r\n    PCM.nSamplesPerSec := SamplesPerSec;\r\n    PCM.nBlockAlign := (Bits div 8) * PCM.nChannels;\r\n    PCM.nAvgBytesPerSec := PCM.nSamplesPerSec * PCM.nBlockAlign;\r\n    PCM.wBitsPerSample := Bits;\r\n    PCM.cbSize := 0;\r\n\r\n    dwSize := SizeOf(DSBUFFERDESC);\r\n    dwFlags := DSBCAPS_STATIC;\r\n    dwBufferBytes := SizeInBytes;//Time * PCM.nAvgBytesPerSec;\r\n    lpwfxFormat := @PCM;\r\n  end;\r\n\r\n  if DirectSound.CreateSoundBuffer(BufferDesc, Buffer, nil) <> DS_OK then\r\n    raise Exception.Create('DirectSound.CreateSoundBuffer[secondary] failed');\r\nend;\r\n\r\nprocedure LoadWAVToSecondaryBuffer(Name: PChar; var Buffer: IDirectSoundBuffer);\r\nvar\r\n  Data: PByteArray;\r\n  FName: TFileStream;\r\n  DataSize: DWord;\r\n  Chunk: string[4];\r\n  Pos: Integer;\r\nbegin\r\n  FName := TFileStream.Create(Name, fmOpenRead);\r\n  try\r\n    Pos := 24;\r\n    SetLength(Chunk, 4);\r\n\r\n    repeat\r\n      FName.Seek(Pos, soFromBeginning);\r\n      FName.Read(Chunk[1], 4);\r\n      Inc(Pos);\r\n    until Chunk = 'data';\r\n\r\n    FName.Seek(Pos + 3, soFromBeginning);\r\n    FName.Read(DataSize, SizeOf(DWord));\r\n\r\n    CreateWriteSecondaryBuffer(SecondarySoundBuffer, 44100, 16, False, DataSize);\r\n\r\n    GetMem(Data, DataSize);\r\n    try\r\n      FName.Read(Data^, DataSize);\r\n      WriteDataToBuffer(Buffer, 0, Data^, DataSize);\r\n    finally\r\n      FreeMem(Data, DataSize);\r\n    end;\r\n  finally\r\n    FName.Free;\r\n  end;\r\nend;\r\n\r\nbegin\r\n  FileName := Trim(ParamStr(1));\r\n  if FileName = '' then\r\n  begin\r\n    WriteLn('Usage: dsplay filename.wav');\r\n    WriteLn('The only supported format is 44100Hz 16bit mono PCM');\r\n    Exit;\r\n  end;\r\n\r\n  if not FileExists(FileName) then\r\n  begin\r\n    WriteLn(Format('File ''%s'' doesn''t exist', [FileName]));\r\n    Exit;\r\n  end;\r\n\r\n  try\r\n    if DirectSoundCreate(nil, DirectSound, nil) <> DS_OK then\r\n      raise Exception.Create('DirectSoundCreate failed');\r\n\r\n    CreateWritePrimaryBuffer;\r\n    LoadWAVToSecondaryBuffer(PWideChar(FileName), SecondarySoundBuffer);\r\n\r\n    if SecondarySoundBuffer.Play(0, 0, 0) <> DS_OK then\r\n      raise Exception.Create('SecondarySoundBuffer.Play failed');\r\n\r\n    // wait till the sund has finished playing\r\n    while True do\r\n    begin\r\n      Sleep(1);\r\n\r\n      if SecondarySoundBuffer.GetStatus(Status) <> DS_OK then\r\n        raise Exception.Create('SecondarySoundBuffer.GetStatus failed');\r\n\r\n      if (Status <> DSBSTATUS_PLAYING) then\r\n        Break;\r\n    end;\r\n\r\n    // Sleep some extra milliseconds, otherwise an audible click\r\n    // will be heard on destroying the sound buffers\r\n    Sleep(SLEEP_EXTRA_TIME_BEFORE_CLOSE);\r\n\r\n  finally\r\n    // Assign nil to variables to release their COM references\r\n    PrimarySoundBuffer := nil;\r\n    SecondarySoundBuffer := nil;\r\n    DirectSound := nil;\r\n  end;\r\nend.\r\n\r\n"
  },
  {
    "path": "src/rec_stdout/README.md",
    "content": "rec_stdout.exe is a simple console utility that will record data from default input source in 44.1KHz mono 16bit signed PCM format\r\nand write it to STDOUT.\r\n\r\nThere are two main scenarios of its use:\r\n\r\n1) rec_stdout.exe >output.raw\r\n\r\nThis will just PCM data to an ever growing file untill you press Ctrl+C (or until the file reaches its max size).\r\n\r\n2) rec_stdout.exe | perl test_rec_stdout.pl\r\n\r\nThis will pipe the data to any other consle application of your choice, including scripting languages.\r\nThis will allow you to examine data continuously."
  },
  {
    "path": "src/rec_stdout/rec_stdout.dpr",
    "content": "// Copyright (C) 2013 Igor Afanasyev, https://github.com/iafan/Hacksby\r\n\r\nprogram rec_stdout;\r\n\r\n// Uses freeware Wave Audio Package components from\r\n// http://www.delphiarea.com/products/delphi-packages/waveaudio/\r\n\r\n{$APPTYPE CONSOLE}\r\n\r\nuses\r\n  Classes,\r\n  SysUtils,\r\n  WaveUtils,\r\n  WaveRecorders,\r\n  Windows;\r\n\r\ntype\r\n  TDataHandler = class(TObject)\r\n    procedure LiveAudioRecorderData(Sender: TObject; const Buffer: Pointer;\r\n      BufferSize: Cardinal; var FreeIt: Boolean);\r\n  end;\r\n\r\nvar\r\n  r: TLiveAudioRecorder;\r\n  s: THandleStream;\r\n  h: TDataHandler;\r\n\r\n  NeedQuit: Boolean = false;\r\n\r\nprocedure TDataHandler.LiveAudioRecorderData(Sender: TObject; const Buffer: Pointer;\r\n  BufferSize: Cardinal; var FreeIt: Boolean);\r\nvar\r\n  i: Integer;\r\nbegin\r\n  FreeIt := True;\r\n  try\r\n    s.WriteBuffer(Buffer^, BufferSize);\r\n  except\r\n    NeedQuit := true;\r\n  end;\r\nend;\r\n\r\nprocedure DispatchMessageLoop;\r\nvar\r\n  Msg: TMsg;\r\nbegin\r\n  while not NeedQuit and GetMessage(Msg, 0, 0, 0) do begin\r\n    TranslateMessage(Msg);\r\n    DispatchMessage(Msg);\r\n    Sleep(0);\r\n  end;\r\nend;\r\n\r\nbegin\r\n  try\r\n    s := THandleStream.Create(GetStdHandle(STD_OUTPUT_HANDLE));\r\n    h := TDataHandler.Create;\r\n    r := TLiveAudioRecorder.Create(nil);\r\n    r.PCMFormat := Mono16bit44100Hz;\r\n    r.BufferLength := 100; // 100ms\r\n    r.OnData := h.LiveAudioRecorderData;\r\n    r.Active := true;\r\n    DispatchMessageLoop;\r\n    r.Active := false;\r\n    r.WaitForStop;\r\n  finally\r\n    r.Free;\r\n    h.Free;\r\n    s.Free;\r\n  end;\r\nend.\r\n"
  },
  {
    "path": "src/rec_stdout/test_rec_stdout.pl",
    "content": "use strict;\r\n\r\nmy $buf;\r\n\r\nmy $n = 0;\r\nfor (0..10) {\r\n    my $numread = sysread(STDIN, $buf, 10000);\r\n    $n += $numread;\r\n    print \"$numread bytes read\\n\";\r\n}\r\n\r\nprint \"$n total bytes read\\n\";\r\n"
  }
]