demo of using the new MUMBLE CLIENT's choice of PIPEWIRE

244 views
Skip to first unread message

Chuck Vaughn

unread,
Jan 2, 2025, 12:54:16 PMJan 2
to iCW - internet CW
mumble now has the option to use PIPEWIRE directly for both input and output
here is a short LIVE ACTION video of that operation:
https://youtu.be/1NhwOujcYzk

using UBUNTU 24.04 on a lenovo laptop thinkpad
with 2 mumble clients running
- one used as sender
- the other used as receiver

pipewire has several connection bays you can use, where you wire up 
inputs and outputs to each other, [even from/to multiple apps(if you like)]
this video uses CARLA as a pipewire connection bay,  via pipewire's  pw-jack bridge mode....



df7t...@gmail.com

unread,
Jan 4, 2025, 10:05:54 AMJan 4
to iCW - internet CW
Hello Chuck,
good idea to introduce Pipewire -- it seems to be the way to go in future.
Recently, I saw an interview with its author and Pipewire, as an additional layer
above Alsa, Pulseaudio, Jack... , seems to facilitate the task of managing audio setups.

From your previous video and your remarks concerning the  timing...

# Define constants for timing calculations 
DIT_DURATION_CONSTANT = 1200
DAH_DURATION_MULTIPLIER = 3.00
INTER_ELEMENT_SPACE_MULTIPLIER = .70
INTER_CHARACTER_SPACE_MULTIPLIER = .50
WORD_SPACE_MULTIPLIER = 2.00# Define constants for timing calculations
DIT_DURATION_CONSTANT = 1200
DAH_DURATION_MULTIPLIER = 3.00
INTER_ELEMENT_SPACE_MULTIPLIER = .70
INTER_CHARACTER_SPACE_MULTIPLIER = .50
WORD_SPACE_MULTIPLIER = 2.00

.. there are lots of factors.

Some of those multipliers may go back to the intention to correct for the fall time of the succeeding band pass filter.

I think, it would be better to leave the duration of DITs, DAHs, element space, character space and  word space according to the normal, Paris-based standard (1 dit, 3 dit, 1 dit, 3 dit and 7 dit) and to avoid multipliers/correction factors.

Seen that the audio is transferred to the sound card by an array, it should be possible to replace the entries for DITs and DAHs
in the array to take account of the fall time of the band pass filter in use.

So -- instead of having a DIT in the array defined like [1,1,1,1,1,1,1,1,1,1,1,1] it would be defined like [0,0,0,0,1,1,1,1,1,1,1,1]
where the number of zeros replacing the 1s at the start is determined by the sample rate and the fall time of the band pass filter.

That solution would be speed-independant. For sure - it comes at a price - the output will be delayed by the fall time.
But I think, for a  CW keyboard and even for paddle input, a delay of about 5 to 8 ms does not really matter.

One might think about the introduction of a "compensation slider", having a range of 0 to 15 ms, which would just reduce the
keyed time of DITs and DAHs. Automagically the element space, character space and  word space would remain normal.

GL, 73
Tom

df7t...@gmail.com

unread,
Jan 4, 2025, 10:17:06 AMJan 4
to iCW - internet CW
"Automagically the element space, character space and  word space would remain normal."

It should read:

_After the band pass filter_  -- the duration of DIT, DAH,  the element space, character space and  word space would
automagically be normal.

73
Tom

df7t...@gmail.com

unread,
Jan 4, 2025, 10:35:13 AMJan 4
to iCW - internet CW
That solution would be like shown in red color,  just below the input signal in


73
Tom

aa0hw

unread,
Jan 5, 2025, 7:50:27 AMJan 5
to iCW - internet CW
I think chatGPT may have done all it can
since i really don't know python, but can help gpt by
pointing out failures in its code etc...
so perhaps a PYTHON MASTER can take the latest code from GPT
and perfect its timing for morse...and upload their solutions to our fourm...

i have attached the latest gpt cw keyboard... the best i could coax out of gpt
after days and hours of trying to correct its non-standard methods of producing
un-perfectly accurate code timing, no matter the speed or sample rate/buffer-frames being use,
  it failed to do so...only repeating previous failed attempts...
so the gpt era of coding this CW keyboard i believe has come to its end...

so calling all PYTHON MASTER CODERs 

that can take a  look at the attached 450 lines of python code
and modify it for perfection, optimization etc.. 
NOTE:  there is also still a deprecated
background shading method that GPT could not solve...

- if the PYTHON MASTERs could upload their versions
we would be happy to test them out and offer feedback
and in addition to jack audio connection kit, alsa, pipewire,
perhaps they can also provide versions for MAC and WINDOWS 11(asio/wasapi(shared & exclusive))

thanks to all that may feel the call....to donate their time and expertise to
furthering the art form of our beloved Morse 

chuck
... 
cwkbcopilot8_d.py

df7t...@gmail.com

unread,
Jan 16, 2025, 10:20:03 PMJan 16
to iCW - internet CW
TIMING

Hello Chuck! -- seen the nice start of this project, it would be too bad not to profit from your hours of work.

I admit, that myself, I could have said
as well
                    "... since i really don't know python ..."

 -- but it seems that I, at least, could identify one part of the problem and offer a solution for that part.

Taking the code appended to your last message (cwkbcopilot8_d.py of January 5, 2025)

lines 27 to 32:

# Define constants for timing calculations
DIT_DURATION_CONSTANT = 1200
DAH_DURATION_MULTIPLIER = 3.00
INTER_ELEMENT_SPACE_MULTIPLIER = 1.00
INTER_CHARACTER_SPACE_MULTIPLIER = .40
WORD_SPACE_MULTIPLIER = 2.10


Changes made:

lines 27 to 32:

# Define constants for timing calculations
DIT_DURATION_CONSTANT = 1200
DAH_DURATION_MULTIPLIER = 3.00
INTER_ELEMENT_SPACE_MULTIPLIER = 1.00
INTER_CHARACTER_SPACE_MULTIPLIER = 1.00
WORD_SPACE_MULTIPLIER = 3.00


I did this, because it looks more like the standard timing and because I assumed the
former values were chosen in trying to correct for imperfect timing of the code.
Now, running the changed code at a speed of 20 wpm, sending the number 5 two times in a row,
resulted in a space between the two 5s of about 240 ms -- it should be 180 ms; or 60 ms (one DIT duration less)

So I looked a bit further down in the code:

lines 58, 59:

        self.dit_duration_ms = base_dit_duration - 0.00  # Added 0 milliseconds to dit duration
        self.dah_duration_ms = base_dah_duration - 1.00  # Added 0 milliseconds to dah duration

Not understanding the "correction" by "-1.00 ms" in line 59, I just canceled it:

lines 58, 59:

        self.dit_duration_ms = base_dit_duration - 0.00  # Added 0 milliseconds to dit duration
        self.dah_duration_ms = base_dah_duration - 0.00  # Added 0 milliseconds to dah duration


Going further down in the code:

line 115 to 135 - where the magic happens :=)

    def generate_wave_for_string(self, string):
        wave_parts = []  # Use a list to gather wave parts

        for char in string:
            if char == '.':
                wave_parts.append(self.TONE_CACHE[self.dit_duration_ms])
                wave_parts.append(self.SILENCE_CACHE[self.inter_element_space_ms])
            elif char == '-':
                wave_parts.append(self.TONE_CACHE[self.dah_duration_ms])
                wave_parts.append(self.SILENCE_CACHE[self.inter_element_space_ms])
            elif char == ' ':
                wave_parts.append(self.SILENCE_CACHE[self.word_space_ms])
            elif char in MORSE_CODE:
                morse_symbol = MORSE_CODE[char]
                for symbol in morse_symbol:
                    if symbol == '.':
                        wave_parts.append(self.TONE_CACHE[self.dit_duration_ms])
                    elif symbol == '-':
                        wave_parts.append(self.TONE_CACHE[self.dah_duration_ms])
                    wave_parts.append(self.SILENCE_CACHE[self.inter_element_space_ms])
                wave_parts.append(self.SILENCE_CACHE[self.inter_character_space_ms])

If I look to the part following "for symbol in morse_symbol:" (the last six lines of this snippet):
After EVERY symbol of a character, one "inter_character_space_ms" is appended; even after the last symbol of the character.
Now when the character is finished, in the last line of the snippet the "inter_character_space_ms" is appended.

That's one part of the timing problem, I think. Now, after the last symbol of a character we have a sum of FOUR DIT durations of silence
(one from line 134, three from line 135).

So ...comming back to my measurement for two 5s in a row. That would nicely explain the difference between the expected three-DIT duration silence between the two 5s and the measure four-DIT duration of silence. (True -- but -- sorry! -- it will not solve all problems)

lines 62 and 82 of the original code:

        self.inter_character_space_ms = INTER_CHARACTER_SPACE_MULTIPLIER * base_dit_duration
...
            self.inter_character_space_ms,
...


To correct the silence time, I have been lazy and just introduced an additional "reduced" (by one DIT duration) time 

after line 62:

        self.inter_character_space_reduced_ms = INTER_CHARACTER_SPACE_MULTIPLIER * base_dit_duration - base_dit_duration

after line 82:

            self.inter_character_space_reduced_ms,


Then I change the part the part following "for symbol in morse_symbol:" to

                for symbol in morse_symbol:
                    if symbol == '.':
                        wave_parts.append(self.TONE_CACHE[self.dit_duration_ms])
                    elif symbol == '-':
                        wave_parts.append(self.TONE_CACHE[self.dah_duration_ms])
                    wave_parts.append(self.SILENCE_CACHE[self.inter_element_space_ms])
 #               wave_parts.append(self.SILENCE_CACHE[self.inter_character_space_ms])
# commented out

and line 137, 138

        # Add silence to the end of the wave
        wave_parts.append(self.SILENCE_CACHE[self.inter_character_space_ms * 3])

to read:

        # Add silence to the end of the wave, reduced by silence after the last symbol of character
        wave_parts.append(self.SILENCE_CACHE[self.inter_character_space_reduced_ms])


Sorry for the long message, but it helped me to check what I have been doing.


Now for the good/bad news:

Good news: at 20 wpm everything looks fine for the silence between two 5s.
At 100 wpm, we have a silence of 40 ms instead of 36 ms (normal) between the 5s.
At 200 wpm, we have a silence of 30 ms instead of 18 ms (normal).

...Well one could say...just offering a bit of Farnsworth at speeds above 100 wpm  :) :):) ....but that would be cheating
So -- there remain some parts of the problem.

The modified code and a recording 20/100/200 wpm is appended.


GL, 73
Tom

 
cwkbcopilot8_d-mod_001.py
two-5s-at-20-100-200-wpm.mp3

df7t...@gmail.com

unread,
Jan 16, 2025, 10:57:24 PMJan 16
to iCW - internet CW
BTW: Thonny is a nice and simple IDE for beginners in Python.
It may be installed under Linux, Windows and Mac OS.


73
Tom

df7t...@gmail.com

unread,
Jan 17, 2025, 2:18:34 AMJan 17
to iCW - internet CW
TIMING

Well I did not copy the modified code into my previous message.
Evidently I have made in error when writing the message 
out of memory (the appended modified code instead, is correct)...

The changes to the constants should read as follows to be in accordance with normal timing:

# Define constants for timing calculations 
DIT_DURATION_CONSTANT = 1200
DAH_DURATION_MULTIPLIER = 3.00
INTER_ELEMENT_SPACE_MULTIPLIER = 1.00 
INTER_CHARACTER_SPACE_MULTIPLIER = 3.00
WORD_SPACE_MULTIPLIER = 7.00 

73
Tom

df7t...@gmail.com

unread,
Jan 17, 2025, 2:45:07 PMJan 17
to iCW - internet CW
TIMING

Just remarked that I have forgotten to mention, that for my tests,
I have removed the "envelope" (3 ms rise/fall time) from the code:

original (line 161):

        return (0.3 * tone * envelope).astype(np.float32)  # Reduced amplitude to 0.3

in "...mod001" (line 163):

        return (0.3 * tone).astype(np.float32)  # Reduced amplitude to 0.3


73
Tom

aa0hw

unread,
Jan 18, 2025, 6:23:49 AMJan 18
to iCW - internet CW
good work on this TOM

i just tested your modified D code,  at 60 wpm
but the space between words is too much
and the inter-character spacing is too much
which is why i had to adjust 'down' the standard values...
i think CHATgpt has not correctly applied the math or is using
a very inferior method for morse code timing...
i adjusted those standard values by ear,  but it would only get close to what it should have been...
the chatGPT code as is,  does not pay attention to the 2nd decimal place...
- it only rounds it up or down to the closest 1st decimal place

i have tried for weeks now to get gpt to correct that...
but it failed...

to begin, with, though,   the code should have operated perfectly
with those standard values...but alas, it did not...

so, until another AI emerges that can be used for free 
or us mere humans can find a cure...
i will hope that more discoveries can still be found... 

thanks again TOM for your hard work and contributions...
c
...
 

df7t...@gmail.com

unread,
Jan 18, 2025, 9:23:05 AMJan 18
to iCW - internet CW
Thank you for testing Chuck!

Well...it has been a trial.

Somehow, I think that the source of the timing problem lies somewhere in the section of

def generate_wave_for_string(self, string):

where the components of the "wave" to be sent are composed and appended one after the other into
an array. I think that looking closely to the code and analyzing in which sequence and 
under which conditions the different spacings 
(inter-element, inter-character, inter-word) are
appended may help -- but I do not really know.

At present, I have no further idea and hope, like you, that some SUPER Ai or (better) SUPER Python
programmer will help out.

73
Tom

df7t...@gmail.com

unread,
Jan 18, 2025, 9:27:23 AMJan 18
to iCW - internet CW
"...
chatGPT code as is,  does not pay attention to the 2nd decimal place...
- it only rounds it up or down to the closest 1st decimal place
..."

Perhaps a "FLOAT" / "INTEGER" type problem?...again I do not know :(

73
Tom

Chuck Vaughn

unread,
Jan 18, 2025, 2:38:57 PMJan 18
to i_...@googlegroups.com
here is a good example of the lack of precision in the current python code,
i also noticed that the speed does not increase incrementally, but it stays the
same for a few wpm's at the high levels,  and then finally jumps higher in speed, 

also,  here is another flaw, showing up in the current python code, 
the space between characters is not the same as show below,
between the letter T and the letter  E,    and between the letter E and the letter S(too short)
- the TOP shows the inaccuracy from the latest python code
- the BOTTOM shows what it should be from a known industry cw keyboard
codetimingpythonerrors1.png

--

---
You received this message because you are subscribed to the Google Groups "iCW - internet CW" group.
To unsubscribe from this group and stop receiving emails from it, send an email to i_cw+uns...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/i_cw/0cf3ea4c-5e61-4911-9744-a6609b14e852n%40googlegroups.com.

df7t...@gmail.com

unread,
Jan 18, 2025, 3:11:56 PMJan 18
to iCW - internet CW
Good example, Chuck

BTW: 
1.) This "simple scope" -- where may I find the plugin?
2.) Is there a way to copy and let send a prefabricated (test) text into the entry window of the present version?
      Or -- to let re-send a text already sent?

73
Tom

S. Steltzer

unread,
Jan 18, 2025, 4:32:37 PMJan 18
to 'joe living' via iCW - internet CW
I hope you guys can find a solution. To spend that much time / effort on something and not end up with a workable program would be a real shame.

73!
Steve

df7t...@gmail.com

unread,
Jan 18, 2025, 4:57:34 PMJan 18
to iCW - internet CW
Thanks Steve! --

and you are right...this CWKeyboard, apart from the problems still being present, could be a very nice thing to have.

The ChatGpt code, to my opinion, is not really bad and working for high CW speeds
Using a debugger etc. it should be possible to find and resolve the remaining bugs.

Another reason, why I find it interesting is, that the code is concentrated on one goal to be achieved, is SHORT (few lines of code) and Python code, as such, is
easy to read "top to down".

The recri keyer and the K3NG keyer, which have been developed over ten years and more, in the meantime are (to me) not so easy to read/understand because over all these years a lot of options have been added and led to very extended code.
I think the K3NG keyer code now has about 10000 lines of code/comments.

I may fully understand Chuck -- having tried to get fixed bugs over weeks -- that one may tend to say "enough is enough".


...Well let's see :)

73
Tom

Chuck Vaughn

unread,
Jan 19, 2025, 6:30:01 AMJan 19
to i_...@googlegroups.com
this is a keyboard that can copy paste, or load a text file
has a lot more functions that the simple keyboard    but does not yet
have any color function choices by user input... 

python code here:

multitask5cwkb.png


df7t...@gmail.com

unread,
Jan 19, 2025, 4:17:43 PMJan 19
to iCW - internet CW
Thanks Chuck!

This version could help in identifying and resolving timing problems by allowing to paste a test message.


Today, I have asked for help on this project on the CWops email list -- I hope some "Python Master" will join this group.



73
Tom

Chuck Vaughn

unread,
Jan 20, 2025, 8:43:30 AMJan 20
to i_...@googlegroups.com
thanks for asking CWops to help....

i tried again this morning for a couple of hours, but could only get GPT to print out
this.... but i was not able to put it in use...or get GPT to convert it ...it kept messing up
so bad...time after time... 

maybe something here is a clue for high precision morse code python timing 

GPT SAYS THIS:
To achieve absolute timing accuracy to 0.1 ms (100 µs) in Python for generating or interpreting Morse code, you need to utilize high-resolution timing functions and ensure the system is optimized for real-time performance. Here's how you can achieve this:

1. Use time.perf_counter()
The time.perf_counter() function provides the highest available resolution timer for measuring short durations in Python. It is precise and unaffected by system clock adjustments.

Example Code:
python
Copy
Edit
import time

# Timing with 0.1 ms (100 µs) precision
def generate_morse_signal(dot_duration_ms):
    dot_duration = dot_duration_ms / 1000  # Convert to seconds
    print("Signal ON")
    start = time.perf_counter()
    while time.perf_counter() - start < dot_duration:
        pass  # Simulate the signal being on
    print("Signal OFF")

# Example: Generate a dot signal for 100 ms
generate_morse_signal(100)
2. Use Real-Time Libraries (Optional)
For more precise control over time-sensitive tasks, consider Python libraries like:

time: For basic timing (like time.sleep()), though not ideal for microsecond accuracy.
threading.Timer or sched: For precise scheduling, but may still be affected by OS delays.
pigpio or RPi.GPIO: For hardware-level precision on Raspberry Pi.
ctypes: To call high-resolution timers from the OS (e.g., Windows' QueryPerformanceCounter).
3. Avoid time.sleep() for Precision
While time.sleep() is convenient, it is not suitable for sub-millisecond precision because the underlying implementation often has scheduling delays.

4. System Considerations
OS Interference: Python scripts run in a non-real-time environment, so background processes can introduce small delays. To mitigate:
Use a real-time operating system or adjust process priority.
Run the script on a dedicated or less-loaded machine.
Hardware Clock: Ensure the system clock has sufficient precision. Consider external hardware (e.g., microcontrollers) for tighter timing requirements.
Example for Morse Code Timing:
python
Copy
Edit
import time

DOT_DURATION = 100  # Duration of a dot in ms

def wait(duration_ms):
    start = time.perf_counter()
    while (time.perf_counter() - start) < (duration_ms / 1000):
        pass  # Busy-wait for precise timing

def send_morse_code(code, dot_duration_ms):
    for symbol in code:
        if symbol == '.':
            print("Dot")
            wait(dot_duration_ms)
        elif symbol == '-':
            print("Dash")
            wait(dot_duration_ms * 3)
        print("Space between symbols")
        wait(dot_duration_ms)  # Space between symbols

# Test Morse Code: "A" (.-)
send_morse_code(".-", DOT_DURATION)
This setup uses time.perf_counter() to ensure tight timing and a busy-wait loop for sub-millisecond accuracy. If sub-millisecond precision must be guaranteed, you may need to consider hardware solutions.

I ASKED GPT TO ANALYZE ITS MOST RECENT CODE    COPILOT_D  and it said this:
The code is reasonably structured but does not use the highest precision timing methods for sub-millisecond accuracy. Here's why:

Timing Logic:

The code calculates Morse timing in milliseconds (duration_ms) and derives the necessary samples using sample_rate.
While this approach is sufficient for many use cases, Python's time.sleep() (or implied equivalents in the JACK process callback) might not provide the consistent sub-millisecond precision needed for extreme timing requirements, such as ±0.1ms.
JACK Framework:

The JACK library ensures low-latency audio processing, which helps with precision to some degree. However, the callback doesn't include a high-precision clock like time.perf_counter_ns() or time.time_ns() for tracking cumulative drift or offsets.
No Use of time.perf_counter_ns():

A high-precision clock like time.perf_counter_ns() (introduced in Python 3.7) is not used to synchronize timing when generating tones or silence. This might introduce slight drift over time in the audio output timing.
Tone Precomputation:

While the code precomputes tones and silence samples for efficiency, it does not account for dynamic timing drift or system-level jitter correction during playback.
Recommendations for High Precision Timing:
If sub-millisecond precision (±0.1ms) is critical:

Switch to High-Precision Timing:

Use time.perf_counter_ns() or time.time_ns() to measure and adjust timing at the nanosecond level. These methods are more precise than relying solely on sample-based calculations.
Real-Time Monitoring:

Continuously monitor the playback progress using a real-time clock to ensure that cumulative drift is detected and corrected.
JACK Callback Validation:

Synchronize the process() callback with a high-resolution clock to verify that the intended timing aligns with actual audio output.
Consider a Real-Time Operating System:

For extreme precision, the choice of OS and kernel configuration can matter. A real-time kernel (e.g., Linux RT kernel) would help minimize system-level jitter.
Reduce Latency and Jitter:

Minimize buffer sizes in the JACK configuration to reduce overall processing latency.
Hardware Sync:

If possible, use external timing hardware or a clock source to achieve absolute precision.
Would you like help integrating perf_counter_ns() or enhancing this code for higher precision?






You said:
COULD the code i just uploaded to you be modifed easily to use high precision timing as you just suggested
ChatGPT said:
ChatGPT
Yes, the code you provided could be modified to use high-precision timing for more accurate and consistent Morse code generation. Currently, the code relies on durations (e.g., dit_duration_ms, dah_duration_ms) that are precomputed and cached as arrays of audio samples. While this approach works well, it doesn't explicitly leverage high-precision timing libraries, such as Python's time module with time.perf_counter() or sched, to ensure precise time intervals during real-time generation.

Key Areas to Modify for High-Precision Timing
Event Timing During the process Callback:

The current implementation relies on the Jack audio processing buffer for real-time audio data. Using high-precision timing can help ensure the precise start and stop of tones during playback.

Replace the reliance on the current_frame logic with precise timing checks, using a high-resolution timer like time.perf_counter().

Waveform Generation on Demand:

Instead of precomputing all waveforms, generate the required tones dynamically based on high-precision timing, ensuring consistency in audio playback.
Process Callback Timing Logic:

Incorporate a mechanism to track when each Morse code element (tone or silence) begins and ends using precise time calculations.
Example Adjustments for High-Precision Timing
Below is a conceptual example of how you could modify the process method to include high-precision timing:

Add Timing Logic
python
Copy
Edit
import time  # High-precision timing

class MorseCodeKeyboard:
    def __init__(self):
        self.current_time = time.perf_counter()
        self.next_event_time = self.current_time  # Time of the next tone/silence transition
        self.is_tone = False  # Flag to track whether tone or silence is active
        self.current_wave = None
        self.processed_chars = 0

    def process(self, frames):
        buffer = self.output_port.get_buffer()
        output = np.zeros(frames, dtype=np.float32)  # Default to silence
       
        current_time = time.perf_counter()
        if self.current_wave is None and self.buffer:
            char = self.buffer.popleft()
            self.current_wave, self.event_durations = self.generate_wave_and_timing(char)
            self.current_frame = 0
            self.next_event_time = current_time + self.event_durations[0]  # Set time for the first event
            self.event_index = 0
            self.is_tone = True

        if self.current_wave is not None:
            remaining_samples = len(self.current_wave) - self.current_frame
            chunk = min(frames, remaining_samples)
           
            # Fill the buffer based on whether it's a tone or silence
            if self.is_tone and current_time >= self.next_event_time:
                self.is_tone = False
                self.event_index += 1
                if self.event_index < len(self.event_durations):
                    self.next_event_time += self.event_durations[self.event_index]
           
            if self.is_tone:
                output[:chunk] = self.current_wave[self.current_frame:self.current_frame + chunk]

            self.current_frame += chunk
            if self.current_frame >= len(self.current_wave):
                self.current_wave = None  # Reset for the next character
       
        buffer[:] = output



df7t...@gmail.com

unread,
Jan 20, 2025, 6:23:03 PMJan 20
to iCW - internet CW
Hello Chuck,

From the cited Ai's response:

"... The code calculates Morse timing in milliseconds (duration_ms) and derives the necessary samples using sample_rate.

While this approach is sufficient for many use cases, Python's time.sleep() (or implied equivalents in the JACK process callback) might not provide the consistent sub-millisecond precision needed for extreme timing requirements, such as ±0.1ms.
"
..."



I do not think that the CW keyboard has the mentioned extreme timing requirements.
 
Even a sound card having  a sample rate of "only" 44.1kHz will provide a sample every  23 microseconds.
If we go to the extreme speed of 200 wpm, the DIT duration is 6 ms corresponding to 260 samples for such a sound card.

Depending upon the code, there will be an "uncertainty" of 1, perhaps 2 samples, corresponding to 50 microseconds. I don't think that's a problem; even at 200 wpm, where the "uncertainty" would result in
less than 1 % error.

I think, the chosen method in your code, to calculate the number of samples for a DIT duration at a given speed and taking this number as the BASIS for DAH- and Inter-XYZ-duration is adequate.

I would be glad to have (already) a broader knowledge of Python, but I have not and so I may only speculate:

My best guess, at present is, that there are some logical mistakes in the building of the wave, which is done
in the appended part of the unmodified code of January 5, 2024. I think the IF / ELIF conditions are not correctly applied. I don't think that Ai's proposals for extreme timing precision will help us.

One could also try to look into Roger, AD5DZ's code of the recri keyer. What I have seen so far, Roger has a
very clear/clean style to do things and we might get an idea of doing the building of the wave in a different manner.

I don't like speculating and really hope that we get some help from outside.

73
Tom
Python-CWKeyboard-snippet.png
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted

df7t...@gmail.com

unread,
Jan 23, 2025, 6:13:40 AMJan 23
to iCW - internet CW
Attached is a short Python script, where I try to improve a bit the logic behind the contruction of the wave.
The script only produces some text output; it's not a keyer :)

This part of the script shows, that I prefer to think in "samples of silence at the end of...XYZ..." --  instead of "inter...XYZ...space":
...
# Define constants for calculations
SAMPLE_RATE = 44100.0 # in kHz, example
CW_SPEED = 80 # in wpm, value set as target, example

DIT_KEYED_SAMPLES = round(1.2 * SAMPLE_RATE / CW_SPEED) # nearest integer number of samples corresponding to the keyed time of a DIT

DAH_KEYED_SAMPLES = DIT_KEYED_SAMPLES * 3

EOE_SILENCE_SAMPLES = DIT_KEYED_SAMPLES     # samples of silence at the end of an element 'EOE' (always present at the end of an element)
EOC_SILENCE_SAMPLES = DIT_KEYED_SAMPLES * 2 # ADDITIONAL samples of silence at the end of a character 'EOC'
EOW_SILENCE_SAMPLES = DIT_KEYED_SAMPLES * 4 # ADDITIONAL samples of silence at the end of a word 'EOW'
...

text output, like shown here:

SAMPLE_RATE [Hz]:  44100.0
CW_SPEED (value set as target) [wpm]:  80
DIT_KEYED_SAMPLES:  662
DAH_KEYED_SAMPLES:  1986
EOE_SILENCE_SAMPLES:  662
EOC_SILENCE_SAMPLES:  1324
EOW_SILENCE_SAMPLES:  2648
SILENCE_SAMPLES_BETWEEN_TWO_CONSECUTIVE_ELEMENTS:  662
SILENCE_SAMPLES_BETWEEN_TWO_CONSECUTIVE_CHARACTERS:  1986
SILENCE_SAMPLES_BETWEEN_TWO_CONSECUTIVE_WORDS:  4634
CW_SPEED_AT_OUTPUT_BASED_ON_DIT_KEYED_SAMPLES (value achieved) [wpm]:  79.93957703927492


A quick and dirty Pseudo Code for producing a real wave is included as a comment in the script:

# Pseudo code:
#     (sample input text = 'PARIS ')
#    
#     1.) for present character ('P') fetch symbol ('.' or '-') from Morse code table
#             append tone (DIT/DAH) for symbol to wave
#             append EOE_SILENCE_SAMPLES to wave
#             repeat for remaining symbols of present character
#    
#     2.) when all symbols for the present character have been appended:
#             append EOC_SILENCE_SAMPLES to wave
#
#     3.) until the last character of present word has been processed:
#             set present character to the next character ('A',...,'S')
#             and go to 1.)
#    
#     4.) if character equals SPACE, meaning the end of the word is reached:
#             append EOW_SILENCE_SAMPLES to wave
#
#     5.) continue with next word --> go to 1.)


73
Tom
CW_keyboard_snippet_for_analysis.py

df7t...@gmail.com

unread,
Jan 23, 2025, 6:13:46 AMJan 23
to iCW - internet CW
Attached is a small Python script, where I try to show some ideas, which in my opinion, could lead to a (more) logical construction of the output signal.
You may run the code, but the output of the script is only text -- no sound -- it is no improved or new CW keyboard :)

As you may see, I prefer to think of periods of silence "appended to some entity" instead of "inter-entity" silence.
To my understanding, that's easier to handle because it is closer to the process of how the output signal is constructed.

Some Pseudo Code, as a possible alternative approach for the CW keybord, is included as comment in the script.

73
Tom
CW_keyboard_snippet_for_analysis.py

df7t...@gmail.com

unread,
Jan 23, 2025, 6:13:52 AMJan 23
to iCW - internet CW
Please find attached some ideas for a different logic in constructing the output signal of the CW keyboard.
You may run the Python script, but it will only produce some text output :)

73
Tom
CW_keyboard_snippet_for_analysis.py

df7t...@gmail.com

unread,
Jan 23, 2025, 6:13:57 AMJan 23
to iCW - internet CW
The attached Python script shows some ideas, which might improve the construction of the output signal of the CW keyboard.
The script only produces a text output. It is no CW keyboard.

73
Tom
CW_keyboard_snippet_for_analysis.py

df7t...@gmail.com

unread,
Jan 25, 2025, 9:39:52 AMJan 25
to iCW - internet CW
cwkbcopilot8_d-mod_002.py
# cwkbcopilot8_d-mod_002.py
#
# BASED ON cwkbcopilot8_d.py of January 5, 2025 by Chuck, AA0HW
# https://groups.google.com/g/i_cw/c/MeLizd5WhoQ/m/G4WTfsymBQAJ
#

# MOD: mostly rely on samples (not on time) and set all keyed and silence durations to normal (PARIS-based)
# https://groups.google.com/g/i_cw/c/MeLizd5WhoQ/m/6cK4DvCSDAAJ
#
# See (e.g.) "def set_wpm(self, wpm)" and "def generate_wave_for_string(self, string)"
# "Smooth envelope" at around line 185, is disabled at present
# _pylint: disable=unused-argument_ inserted (as comment without the under-scores) at some places to avoid some (unimportant) Warnings
#
#
# (personal note: start of qjackctl: qjackctl -a ~/thomas/Documents/Jack/py-cwkb.xml
#  -- after that _activate_ the according Patchbay file and no new wiring
#     is needed)
#
# There are still issues with the timing, e.g: Additional silence before the start of a new character
# This additional silence duration is about 0 to 20 ms and differs for different CW speed settings.
# The silence duration _after_ a charcter, 3 DIT duration, is correct for all speeds (tested) -- the problem
# looks like there is a "DELAY"/"HESITATION" before the next character starts.
#
Message has been deleted

Chuck Vaughn

unread,
Jan 25, 2025, 9:51:40 AMJan 25
to i_...@googlegroups.com
your attachment notice says  cwkbcopilot  but the actual attachment name is multitask... ?

On Sat, Jan 25, 2025 at 8:48 AM df7t...@gmail.com <df7t...@gmail.com> wrote:
cwkbcopilot8_d-mod_002.py

Somehow -- the Post message button acted too early -- please find attached a modified file for the cwkbcopilot

df7t...@gmail.com

unread,
Jan 25, 2025, 9:55:59 AMJan 25
to iCW - internet CW
Sorry Chuck --
I'll check that! Please wait for the next message.

Tom

Chuck Vaughn

unread,
Jan 25, 2025, 10:01:34 AMJan 25
to i_...@googlegroups.com
i confirm your findings...
 There are still issues with the timing, e.g: Additional silence before the start of a new character
# This additional silence duration is about 0 to 20 ms and differs for different CW speed settings.
strange that it is not consistent and mysteriously for no found reason changes durations of extra gap phenomena ...

df7t...@gmail.com

unread,
Jan 25, 2025, 10:13:33 AMJan 25
to iCW - internet CW
The correct name of the modified file is "cwkbcopilot8_d-mod_002"

It is attached with the correct name to this message.

Chuck: I have tested the correct "_after_ character silence durations" by
temporarily not having silence there but a tone at a reduced level (so it would
differ from the keyed tone of DITs and DAHs).

That showed clearly that there is some additional silence before the start of a new
character (in Audacity, I could see the correct DITs, DAHs, and 3 DITs (end of character) at reduced tone level PLUS some unwanted silence of 0 to 20 ms)


73
Tom
cwkbcopilot8_d-mod_002.py

df7t...@gmail.com

unread,
Jan 25, 2025, 10:29:00 AMJan 25
to iCW - internet CW
" extra gap phenomena "

It might be related to the time needed by the GUI.
That could be tested by deleting the GUI and making experiments with a hardcoded test message string.
Like "PARIS PARIS "

I still hope, we get some extra help.


73
Tom


On Saturday, 25 January 2025 at 16:01:34 UTC+1 aa0hw wrote:

Chuck Vaughn

unread,
Jan 25, 2025, 11:45:47 AMJan 25
to i_...@googlegroups.com
i may have not provided you with the 'other' solution GPT came up with
when converting JACK to ALSA audio python ENGINE
this code is using standard values(except for word space)

i think the timing code is different somewhat that what was being issued for the jack versions

df7t...@gmail.com

unread,
Jan 25, 2025, 3:13:07 PMJan 25
to iCW - internet CW
Thanks for  the ALSAaudio (copilot9) code Chuck!

If we ever try to get a cross-platform solution, then having different approaches (audio, GUI modules) available, will surely help.

     ....back to the EXTRA gap :)   Perhaps the copilot in airplane n°8 is still learning and hesitates sometimes :) :) :)

73
Tom

df7t...@gmail.com

unread,
Jan 25, 2025, 4:03:27 PMJan 25
to iCW - internet CW
EXTRA gap in cwkbcopilot8_d

Perhaps we'll have to enter into the world of Python multiprocessing/multithreading to solve timing issues --- but I prefer to avoid that --- it may rapidly become
very complicated.

73
Tom

df7t...@gmail.com

unread,
Jan 26, 2025, 3:20:35 PMJan 26
to iCW - internet CW
cwkbcopilot8_d


at present:

CWandPython.gif

Anyone out there to contribute to this CW keyboard Python project ?

CU 73
Tom

S. Steltzer

unread,
Jan 26, 2025, 6:58:30 PMJan 26
to 'joe living' via iCW - internet CW
Oh man Tom,  been there done that!!!!  Those are the kinds of problems I eventually have solved in my sleep. Seriously, just wake up and know the answer. Usually in the wee hours of the morning to boot. And have to get right to the computer and start writing code. Strange but maybe you will get lucky like that too.  

73!
Steve

df7t...@gmail.com

unread,
Feb 7, 2025, 2:51:51 PMFeb 7
to iCW - internet CW
Thanks Steve!

I think that precomputing the audio for all characters/punctuation marks and other signs in CW (like the ligatures <AR>, <AS>, <KN>, <SK> etc.) for the given speed would help to reduce the time for concatenating/appending all parts of a string into a Morse Code audio "wave". This is in contrast to the current concatenation of DITs and DAHs  and might reduce timing issues.

73
Tom 

aa0hw

unread,
Feb 22, 2025, 9:23:38 AMFeb 22
to iCW - internet CW
NEW AI chimes in for improving the PYTHON CW KEYBOARD code
  1.  better timing, using any sound card sample rate
   2.  finally fixed the deprecated background shading using proper CSS STYLING

see attached file for GROK's comments about how it improved the CW KB PYTHON CODE

and the NEW, IMPROVED,  python CW KEYBOARD CODE it created is located here:

grokBACKGROUNDshading.png

cwkbGROKmods1.txt

df7t...@gmail.com

unread,
Feb 22, 2025, 12:23:39 PMFeb 22
to iCW - internet CW
Thank you Chuck! --

what does "GROK" mean - / stand for  ?

73
Tom

Chuck Vaughn

unread,
Feb 22, 2025, 2:31:11 PMFeb 22
to i_...@googlegroups.com

df7t...@gmail.com

unread,
Feb 22, 2025, 7:23:05 PMFeb 22
to iCW - internet CW
Thanks again Chuck,

Now I have learned that (in my simplified English) grok means something like "deep understanding"
and goes back to a book:

Stranger in a Strange Land  by Robert A. Heinlein (1961), where he invented the word for his fictitious Martian language.

73
Tom

aa0hw

unread,
Feb 24, 2025, 11:17:46 AMFeb 24
to iCW - internet CW
Here is a new PYTHON CW KEYBOARD for  CW WHOLE WORD SOUND 'head' copy practice
 that both GROK and GPT A-I  are collaborating on...
(still in early stages of DEVELOPMENT   some bugs still..but nothing to stop from testing it out...
at least this is a working 1st model that needs more testing... )
 
cwpracticekeyboard.png
can be downloaded and tested here:

copy / paste a text file into the TOP SCREEN area...
set WPM and CW PITCH you want to receive at...
hit SEND MORSE CODE and start listening....

idea is to pick a text file, start sending it...
and when you hear a word that you did not copy, 
hit one of the number keys to go back that many words
in the file and start again... keep going back until you can
fully copy that word...   etc... if you can not copy after so many
attempts,   you can hit the minus button on the right side of the WPM GUI BOX area, 
to slow it down a bit and try again....one you get it,  speed back up to your original speed and continue  etc...   

thanks for anyone that would test this program and give some feedback here in the forum...
to help us, help the AI's  to get it working better  etc...
c
...


S. Steltzer

unread,
Feb 25, 2025, 8:57:27 AMFeb 25
to 'joe living' via iCW - internet CW
What compiler / IDE are you  guys using?

Chuck Vaughn

unread,
Feb 25, 2025, 9:49:36 AMFeb 25
to i_...@googlegroups.com
I am just copy/pasting whatever the A I  prints into a text file,  
naming is "whatever.py"   and putting that file into a virtual python environment folder...
where you have to make sure and install the python necessaries to run the program... 
pip3 install - this and that  etc...  the AI will tell you what pip3 installs you will need if you upload the program to it and ask it...
once the file is loaded into the same folder that the python VENV runs in... it just runs 
load the program by using the prefix  python3   and then the file name   whateveryoucallit.py  
i,  just this morning,  got GPT to improve this PRACTICE SENDING CW KEYBOARD APP
where you can go back up to 9 words and repeat and continue sending... to resend words that were not copied the first time through...
you can now also hit the LEFT/RIGHT arrow button while it is sending to
slow it down or speed it up WPM,       so with the left hand on the number row
to go back to a missed copied word(s),  you can hit that number and go back
a bunch of times...  and if you still can not copy that missed word(s),  you can keep going back again, while
the right hand is hitting the left arrow button to slow it down a few wpm's until you finally COPY the word
... then slowly speed it back up to the original speed while resending by hitting the 'right' number key,  to make sure you still
'get' that word at the original speed,  then continue to move on....  

i also got the AI to put a yellow highlight over the word being sent,  and it will also keep scrolling so that you can always look at the text
to see where you are at,   and what word is being sent....  

you can also adjust the word spacing to put quite a bit more space between words to give more time to hear each word for a new set of vocabulary
word sets  etc...    

it uses a cosine edge that sounds very smooth and has a way to add up to 20 percent of extra weight to compensate for the cosine edging process, 
that tends to shorten each element length in the way it sounds...so that if you don't like LIGHT keying you can increase it back, until it sounds best...you can
also take away cycles from each element by up to 20 percent(you can change that percent in the code itself and re-save)
the weight by + or - goes 1 cycle at the current cw pitch  up or down  to compensate for light sounding or heavy sounding elements...  

i will make a demo video of this latest QRQ CW TEXT FILE SENDING KEYBOARD version soon and post it here...

On Tue, Feb 25, 2025 at 7:57 AM S. Steltzer <wf...@fastmail.com> wrote:
What compiler / IDE are you  guys using?

--

---
You received this message because you are subscribed to the Google Groups "iCW - internet CW" group.
To unsubscribe from this group and stop receiving emails from it, send an email to i_cw+uns...@googlegroups.com.

df7t...@gmail.com

unread,
Feb 25, 2025, 12:55:39 PMFeb 25
to iCW - internet CW
On Tue, Feb 25, 2025 at 7:57 AM S. Steltzer  wrote:
What compiler / IDE are you  guys using?


Hello Steve,

Thonny is a simple IDE for beginners in Python (like me) -- it's available for Linux (and other OSs).
It offers some basic debug tools as well.

                                   https://thonny.org/

The present version of Python is something like 3.10....

73
Tom






S. Steltzer

unread,
Feb 25, 2025, 1:03:17 PMFeb 25
to 'joe living' via iCW - internet CW
Ok, I'll check it out Tom, thanks.

73!
Steve
--

---
You received this message because you are subscribed to the Google Groups "iCW - internet CW" group.
To unsubscribe from this group and stop receiving emails from it, send an email to i_cw+uns...@googlegroups.com.

Chuck Vaughn

unread,
Feb 26, 2025, 1:16:19 PMFeb 26
to i_...@googlegroups.com
Here is the VIDEO demo of the latest version of a 
PYTHON PROGRAM to PRACTICE CW COPY
a TEXT sending CW KEYBOARD
with adjustable
       speed,   pitch,   space between words,   element weight
with the ability to go back up to 9 words to resend and continue so you 
can repeat as many times as you like   to learn new word sounds 

copypracticekeyboardsnapper.png

this python program,  if you would like to test it(LINUX/jack audio connection kit)
is downloadable here written by GROK3 and GPT4 : 

if you would like to try it on WINDOWS or MAC
just upload the file to your chosen AI and have it convert it to work on that OS and audio system you choose...

S. Steltzer

unread,
Feb 26, 2025, 6:26:03 PMFeb 26
to 'joe living' via iCW - internet CW

Nice work!

Reply all
Reply to author
Forward
0 new messages