一、应用概述

1.1 应用简介

摩斯电码(Morse Code)是一款文本与摩斯电码互转工具,支持将普通文本编码为摩斯电码(由点.和划-组成),以及将摩斯电码解码为文本。同时提供可视化符号显示、编码对照表查询、转换历史记录、音频信号播放和灯光信号模拟等功能。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了编码映射算法、双向转换、可视化渲染、音频信号生成和定时器控制等关键技术。

1.2 核心功能

功能模块 功能描述 技术实现 设计考量
编码功能 文本转摩斯电码 字符映射字典 支持大小写
解码功能 摩斯电码转文本 反向映射字典 容错处理
可视化符号 符号化显示(●/▬) 字符替换渲染 直观展示
编码对照表 完整摩斯码查询 网格渲染 A-Z, 0-9
历史记录 转换记录保存 数组存储+持久化 最近使用
字符统计 字数/码字数统计 计数 信息量分析
音频播放 摩斯码声音信号 Audio API + 定时器 点/划时长控制
灯光模拟 闪光灯模拟信号 屏幕闪烁+定时器 同步音频

1.3 应用架构

摩斯电码应用采用分层架构:

  1. UI表现层:文本输入/输出区域、摩斯码展示、对照表、历史记录列表。
  2. 业务逻辑层:编码引擎、解码引擎、映射字典管理器、信号播放器。
  3. 数据持久层:使用Preferences API存储转换历史记录。

二、编码解码算法

2.1 摩斯电码映射字典

interface MorseCharacter {
  character: string;
  morse: string;
  category: 'letter' | 'digit' | 'punctuation' | 'prosign';
  note: string;
}

class MorseDictionary {
  // 字母映射
  private static readonly LETTER_MAP: Record<string, string> = {
    'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
    'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
    'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
    'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
    'Y': '-.--', 'Z': '--..'
  };
  
  // 数字映射
  private static readonly DIGIT_MAP: Record<string, string> = {
    '0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-',
    '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.'
  };
  
  // 标点符号映射
  private static readonly PUNCTUATION_MAP: Record<string, string> = {
    '.': '.-.-.-', ',': '--..--', '?': '..--..', '!': '-.-.--',
    ':': '---...', ';': '-.-.-.', '"': '.-..-.', '\'': '.----.',
    '(': '-.--.', ')': '-.--.-', '[': '-.--.', ']': '-.--.-',
    '{': '-.--.', '}': '-.--.-', '&': '.-...', '*': '-..-',
    '@': '.--.-.', '/': '-..-.', '\\': '-..-.', '-': '-....-',
    '+': '.-.-.', '=': '-...-', '_': '..--.-', '$': '...-..-',
    ' ': '/'
  };
  
  // 完整映射
  private static readonly FULL_MAP: Record<string, string> = {
    ...MorseDictionary.LETTER_MAP,
    ...MorseDictionary.DIGIT_MAP,
    ...MorseDictionary.PUNCTUATION_MAP
  };
  
  // 反向映射
  private static reverseMap: Record<string, string> | null = null;
  
  private static getReverseMap(): Record<string, string> {
    if (!this.reverseMap) {
      this.reverseMap = {};
      for (const [char, morse] of Object.entries(this.FULL_MAP)) {
        this.reverseMap[morse] = char;
      }
    }
    return this.reverseMap;
  }
  
  // 编码:文本 → 摩斯码
  static encode(text: string): string {
    return text.toUpperCase()
      .split('')
      .map(char => this.FULL_MAP[char] || '?')
      .join(' ');
  }
  
  // 解码:摩斯码 → 文本
  static decode(morse: string): string {
    const reverseMap = this.getReverseMap();
    return morse
      .split(' / ')  // 单词分隔
      .map(word => word
        .split(' ')   // 字母分隔
        .map(code => reverseMap[code] || '?')
        .join('')
      )
      .join(' ');
  }
  
  // 获取所有字符的完整列表(用于对照表)
  static getAllCharacters(): MorseCharacter[] {
    const chars: MorseCharacter[] = [];
    
    for (const [char, morse] of Object.entries(this.LETTER_MAP)) {
      chars.push({ character: char, morse, category: 'letter', note: '' });
    }
    for (const [char, morse] of Object.entries(this.DIGIT_MAP)) {
      chars.push({ character: char, morse, category: 'digit', note: '' });
    }
    for (const [char, morse] of Object.entries(this.PUNCTUATION_MAP)) {
      if (char === ' ') continue;
      chars.push({ character: char, morse, category: 'punctuation', note: '' });
    }
    
    return chars;
  }
  
  // 验证是否为有效的摩斯码
  static isValidMorseCode(code: string): boolean {
    return /^[.\- /]+$/.test(code.trim());
  }
  
  // 获取字符类别
  static getCharacterCategory(char: string): 'letter' | 'digit' | 'punctuation' | 'unknown' {
    if (/[A-Za-z]/.test(char)) return 'letter';
    if (/[0-9]/.test(char)) return 'digit';
    if (char in this.PUNCTUATION_MAP) return 'punctuation';
    return 'unknown';
  }
}

2.2 可视化渲染

class MorseVisualizer {
  // 将摩斯码转换为可视化符号
  static visualize(morse: string): string {
    return morse
      .replace(/\./g, '●')  // 点
      .replace(/-/g, '▬')   // 划
      .replace(/ /g, ' ')   // 字符间空格
      .replace(/\//g, ' / '); // 单词分隔
  }
  
  // 生成HTML格式的可视化
  static generateVisualHtml(morse: string): string {
    let html = '';
    const parts = morse.split(' / ');
    
    for (let w = 0; w < parts.length; w++) {
      if (w > 0) html += '<span class="word-sep"> / </span>';
      const letters = parts[w].split(' ');
      
      for (let l = 0; l < letters.length; l++) {
        if (l > 0) html += '<span class="char-sep"> </span>';
        
        for (const symbol of letters[l]) {
          if (symbol === '.') {
            html += '<span class="dot">●</span>';
          } else if (symbol === '-') {
            html += '<span class="dash">▬</span>';
          }
        }
      }
    }
    
    return html;
  }
  
  // 生成信号时序数据
  static generateSignalTiming(morse: string): number[] {
    const timing: number[] = [];
    const UNIT = 100; // 基本时间单位(毫秒)
    
    // 点 = 1单位
    // 划 = 3单位
    // 字符内间隔 = 1单位
    // 字符间间隔 = 3单位
    // 单词间间隔 = 7单位
    
    for (const char of morse) {
      switch (char) {
        case '.':
          timing.push(UNIT);     // 亮
          timing.push(UNIT);     // 灭(间隔)
          break;
        case '-':
          timing.push(UNIT * 3); // 亮
          timing.push(UNIT);     // 灭(间隔)
          break;
        case ' ':
          timing.push(UNIT * 2); // 额外间隔(字符间共3单位,已有一个单位间隔)
          break;
        case '/':
          timing.push(UNIT * 4); // 额外间隔(单词间共7单位)
          break;
      }
    }
    
    return timing;
  }
}

三、音频信号播放

3.1 信号播放器

class MorseSignalPlayer {
  private isPlaying: boolean = false;
  private audioContext: AudioContext | null = null;
  private oscillator: OscillatorNode | null = null;
  private gainNode: GainNode | null = null;
  private timerId: number | null = null;
  private timingSequence: number[] = [];
  private currentIndex: number = 0;
  private onPlayStateChange: ((playing: boolean) => void) | null = null;
  
  private static readonly FREQUENCY = 800; // 800Hz
  private static readonly UNIT = 100; // 100ms基本单位
  
  async play(morse: string): Promise<void> {
    this.stop();
    
    this.timingSequence = MorseVisualizer.generateSignalTiming(morse);
    if (this.timingSequence.length === 0) return;
    
    this.isPlaying = true;
    this.currentIndex = 0;
    this.onPlayStateChange?.(true);
    
    this.audioContext = new AudioContext();
    this.gainNode = this.audioContext.createGain();
    this.gainNode.connect(this.audioContext.destination);
    this.gainNode.gain.value = 0.5;
    
    this.playNext();
  }
  
  private playNext(): void {
    if (this.currentIndex >= this.timingSequence.length) {
      this.stop();
      return;
    }
    
    const duration = this.timingSequence[this.currentIndex];
    const isOn = this.currentIndex % 2 === 0; // 偶数索引为亮,奇数索引为灭
    
    if (isOn) {
      this.startTone();
    } else {
      this.stopTone();
    }
    
    this.timerId = setTimeout(() => {
      this.stopTone();
      this.currentIndex++;
      this.playNext();
    }, duration);
  }
  
  private startTone(): void {
    if (!this.audioContext || !this.gainNode) return;
    
    this.oscillator = this.audioContext.createOscillator();
    this.oscillator.type = 'sine';
    this.oscillator.frequency.value = MorseSignalPlayer.FREQUENCY;
    this.oscillator.connect(this.gainNode);
    this.oscillator.start();
  }
  
  private stopTone(): void {
    if (this.oscillator) {
      this.oscillator.stop();
      this.oscillator.disconnect();
      this.oscillator = null;
    }
  }
  
  stop(): void {
    this.stopTone();
    if (this.timerId !== null) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
    this.isPlaying = false;
    this.currentIndex = 0;
    this.onPlayStateChange?.(false);
    
    if (this.audioContext) {
      this.audioContext.close();
      this.audioContext = null;
    }
  }
  
  setOnPlayStateChange(callback: (playing: boolean) => void): void {
    this.onPlayStateChange = callback;
  }
  
  getIsPlaying(): boolean {
    return this.isPlaying;
  }
}

四、UI交互设计

4.1 编码解码界面

@Component
struct MorseCodeView {
  @State inputText: string = '';
  @State outputMorse: string = '';
  @State mode: 'encode' | 'decode' = 'encode';
  @State isPlaying: boolean = false;
  @State charCount: number = 0;
  @State morseCount: number = 0;
  
  private player = new MorseSignalPlayer();
  
  convert(): void {
    if (!this.inputText.trim()) {
      this.outputMorse = '';
      this.charCount = 0;
      this.morseCount = 0;
      return;
    }
    
    if (this.mode === 'encode') {
      this.outputMorse = MorseDictionary.encode(this.inputText);
      this.charCount = this.inputText.replace(/\s/g, '').length;
      this.morseCount = this.outputMorse.replace(/[^.\-]/g, '').length;
    } else {
      if (MorseDictionary.isValidMorseCode(this.inputText)) {
        this.outputMorse = MorseDictionary.decode(this.inputText);
        this.charCount = this.outputMorse.length;
        this.morseCount = this.inputText.replace(/[^.\-]/g, '').length;
      } else {
        this.outputMorse = '⚠️ 无效的摩斯码格式';
      }
    }
  }
  
  playSound(): void {
    if (this.isPlaying) {
      this.player.stop();
    } else if (this.mode === 'encode' && this.outputMorse) {
      this.player.play(this.outputMorse);
    }
  }
  
  build() {
    Column() {
      // 模式切换
      Row() {
        Button('📝 编码(文本→摩斯)')
          .backgroundColor(this.mode === 'encode' ? '#4CAF50' : '#E0E0E0')
          .fontColor(this.mode === 'encode' ? '#FFFFFF' : '#333')
          .borderRadius(20)
          .onClick(() => { this.mode = 'encode'; this.convert(); })
        
        Button('🔓 解码(摩斯→文本)')
          .backgroundColor(this.mode === 'decode' ? '#2196F3' : '#E0E0E0')
          .fontColor(this.mode === 'decode' ? '#FFFFFF' : '#333')
          .borderRadius(20)
          .onClick(() => { this.mode = 'decode'; this.convert(); })
      }
      .padding(8)
      
      // 输入区域
      TextInput({
        text: this.inputText,
        placeholder: this.mode === 'encode' ? '输入要编码的文本...' : '输入摩斯电码(. - / 空格)...'
      })
        .width('90%')
        .height(100)
        .backgroundColor('#FFFFFF')
        .borderRadius(8)
        .padding(12)
        .onChange((v) => {
          this.inputText = v;
          this.convert();
        })
      
      // 转换按钮
      Button('🔄 转换')
        .width('90%')
        .height(40)
        .backgroundColor('#FF9800')
        .borderRadius(20)
        .onClick(() => this.convert())
      
      // 输出区域
      Column() {
        Row() {
          Text(this.mode === 'encode' ? '摩斯电码:' : '解码结果:')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
          
          if (this.outputMorse) {
            Button(this.isPlaying ? '⏹ 停止' : '▶️ 播放')
              .fontSize(12)
              .backgroundColor(this.isPlaying ? '#E53935' : '#4CAF50')
              .borderRadius(12)
              .onClick(() => this.playSound())
          }
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        
        // 可视化展示
        Text(this.mode === 'encode' ? 
          MorseVisualizer.visualize(this.outputMorse) : 
          this.outputMorse)
          .fontSize(this.mode === 'encode' ? 20 : 16)
          .fontFamily('monospace')
          .lineHeight(28)
          .padding(12)
          .backgroundColor('#F5F5F5')
          .borderRadius(8)
          .width('100%')
        
        // 统计信息
        if (this.charCount > 0) {
          Row() {
            Text(`📊 字符数: ${this.charCount} | 码符号数: ${this.morseCount}`)
              .fontSize(12)
              .fontColor('#666666')
          }
          .width('100%')
          .margin({ top: 8 })
        }
      }
      .width('90%')
      .padding(12)
      .backgroundColor('#FFFFFF')
      .borderRadius(8)
      .margin({ top: 12 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .padding(16)
  }
}

五、总结

5.1 核心技术要点

  1. 完整的摩斯电码映射字典:支持A-Z字母、0-9数字、常用标点符号,共50+字符。
  2. 双向编码解码:编码(文本→摩斯码)和解码(摩斯码→文本)的完整双向转换。
  3. 可视化渲染:将点(.)和划(-)转换为更直观的符号(●/▬),提升可读性。
  4. 信号时序生成:根据摩斯电码标准时序(点=1单位,划=3单位,间隔=1/3/7单位)生成精确的时序信号。
  5. 音频信号播放:使用Web Audio API生成800Hz正弦波信号,真实模拟摩斯电码声音。
  6. 编码对照表:完整展示所有字符的摩斯电码对照表。

5.2 扩展方向

  1. 灯光信号模拟:通过屏幕闪烁或相机闪光灯模拟摩斯灯光信号。
  2. 学习模式:交互式学习摩斯电码,从慢到快逐步练习。
  3. 自动识别解码:通过麦克风录制声音,自动识别解码摩斯电码。
  4. 实时通信编码:通过蓝牙或网络实时发送/接收摩斯电码。
  5. 自定义编码:支持用户自定义字符编码。

5.3 核心代码量统计

模块 核心代码行数 接口数 组件数
摩斯电码字典 120 6 -
可视化渲染器 60 3 -
信号播放器 100 4 -
UI组件 240 4 3
总计 520 17 3

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐