Find ASCII codes and replace with characters
Warning: The code below is intended to be compatible with the original post.
Its main benefit is that it retains all of the useful detail discovered in the pattern match (unmatched substrings, relative position of matches), eliminating the need to rescan the line. I'd expect it to be faster for that reason.

It retains the problem that chr is a blunt instrument that can effortlessly
consume many digits (not just one, two, or three), using "wrapping"
(a.k.a. modulo 256 math) to scale the resulting integer down to a legal byte value,
possibly not the one intended.

In other words, this code would fail the conversion of a string with ASCII codes 
immediately followed by digits, e.g. 
if "From A1 to z52" needs to be expressable as "From #651 to #12252".
So, a different approach may be needed.

One approach is for the conversion step to convert the digit string to integer digits one digit at a time,
doing the (digits*10 + digit) math and stopping (or actually backing up one digit) 
when a value of 256 or greater is reached.

Another approach, probably faster, is for the conversion step to convert a substring (up to three digits) of
the digit string to an integer and if it is 256 or greater "back up one digit", dividing
the integer by 10. 

In either case, any unused/backed-up digits would be appended to the result
of chr applied to the integer.

In either of these approaches, but especially the first,
some work might be saved by having the regex match at most
three digits instead of any number of digits -- leaving the rest as unmatched characters 
at the head of the next "surrounding text" part. 
That way, there would be no need to test for integer overflow from way too many digits.

A "higher-tech" approach could go further, enabling the current simple 
$parts[$i] = chr($parts[$i]) conversion to be retained.
This would require beefing up the regex so that it matched, in order of preference, 
a 3-digit number less than 256 OR a 2-digit number (that possibly had another 
unmatched digit following it) OR a 1-digit number (that was not followed by another digit).

That seems like a lot of trouble, except maybe for a regex aficionado, so I'd probably opt for the middle approach, converting and testing a 3-digit substring.

&lt;?php
    $line= 'stuff #65 more #66  and #67; other';
    // break out any #numbers that should be translated to ascii, 
    // retaining the surrounding text and the numbers parts.
    // The '#' parts are kept outside the parens to be matched and discarded.
    $parts = preg_split("/\#(\d+)/",$line,NULL,PREG_SPLIT_DELIM_CAPTURE);
    $count = count($parts);
    // Even-indexed entries (possibly empty) contain unchanging surrounding text.

    // Odd-indexed entries are the numbers part of #numbers, so convert them.
    for ($i = 1; $i &lt; $count; $i += 2) {
        $parts[$i] = chr($parts[$i]); // Problem here for input &gt;= 256.
    }

    $line = implode($parts);
?&gt;