From bbd85885df1b566d9b5264690e244ee9870810ed Mon Sep 17 00:00:00 2001 From: starainrt Date: Tue, 4 Jan 2022 14:18:38 +0800 Subject: [PATCH] add beep function --- beep_darwin.go | 28 ++++++++++ beep_linux.go | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ beep_test.go | 37 +++++++++++++ beep_windows.go | 41 ++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 beep_darwin.go create mode 100644 beep_linux.go create mode 100644 beep_test.go create mode 100644 beep_windows.go diff --git a/beep_darwin.go b/beep_darwin.go new file mode 100644 index 0000000..f115f86 --- /dev/null +++ b/beep_darwin.go @@ -0,0 +1,28 @@ +// +build darwin + +package staros + +import ( + "os" + "os/exec" +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 0.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 0 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) error { + osa, err := exec.LookPath("osascript") + if err != nil { + // Output the only beep we can + _, err = os.Stdout.Write([]byte{7}) + return err + } + + cmd := exec.Command(osa, "-e", `beep`) + return cmd.Run() +} \ No newline at end of file diff --git a/beep_linux.go b/beep_linux.go new file mode 100644 index 0000000..e077d52 --- /dev/null +++ b/beep_linux.go @@ -0,0 +1,138 @@ +// +build linux + +package staros + +import ( + "errors" + "os" + "syscall" + "time" + "unsafe" +) + +// Constants +const ( + // This number represents the fixed frequency of the original PC XT's timer chip, which is approximately 1.193 MHz. This number + // is divided with the desired frequency to obtain a counter value, that is subsequently fed into the timer chip, tied to the PC speaker. + clockTickRate = 1193180 + + // linux/kd.h, start sound generation (0 for off) + kiocsound = 0x4B2F + + // linux/input-event-codes.h + evSnd = 0x12 // Event type + sndTone = 0x02 // Sound +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 440.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 200 +) + +// inputEvent represents linux/input.h event structure. +type inputEvent struct { + Time syscall.Timeval // time in seconds since epoch at which event occurred + Type uint16 // event type + Code uint16 // event code related to the event type + Value int32 // event value related to the event type +} + +// ioctl system call manipulates the underlying device parameters of special files. +func ioctl(fd, name, data uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, name, data) + if e != 0 { + return e + } + + return nil +} + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +// +// On Linux it needs permission to access `/dev/tty0` or `/dev/input/by-path/platform-pcspkr-event-spkr` files for writing, +// and `pcspkr` module must be loaded. User must be in correct groups, usually `input` and/or `tty`. +// +// If it can not open device files, it will fallback to sending Bell character (https://en.wikipedia.org/wiki/Bell_character). +// For bell character in X11 terminals you can enable bell with `xset b on`. For console check `setterm` and `--blength` or `--bfreq` options. +// +// On macOS this just sends bell character. Enable `Audible bell` in Terminal --> Preferences --> Settings --> Advanced. +// +// On Windows it uses Beep function via syscall. +// +// On Web it plays hard coded beep sound. +func Beep(freq float64, duration int) error { + if freq == 0 { + freq = DefaultFreq + } else if freq > 20000 { + freq = 20000 + } else if freq < 0 { + freq = DefaultFreq + } + + if duration == 0 { + duration = DefaultDuration + } + + period := int(float64(clockTickRate) / freq) + + var evdev bool + + f, err := os.OpenFile("/dev/tty0", os.O_WRONLY, 0644) + if err != nil { + e := err + f, err = os.OpenFile("/dev/input/by-path/platform-pcspkr-event-spkr", os.O_WRONLY, 0644) + if err != nil { + e = errors.New("beeep: " + e.Error() + "; " + err.Error()) + + // Output the only beep we can + _, err = os.Stdout.Write([]byte{7}) + if err != nil { + return errors.New(e.Error() + "; " + err.Error()) + } + + return nil + } + + evdev = true + } + + defer f.Close() + + if evdev { // Use Linux evdev API + ev := inputEvent{} + ev.Type = evSnd + ev.Code = sndTone + ev.Value = int32(freq) + + d := *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev)) + + // Start beep + f.Write(d[:]) + + time.Sleep(time.Duration(duration) * time.Millisecond) + + ev.Value = 0 + d = *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev)) + + // Stop beep + f.Write(d[:]) + } else { // Use ioctl + // Start beep + err = ioctl(f.Fd(), kiocsound, uintptr(period)) + if err != nil { + return err + } + + time.Sleep(time.Duration(duration) * time.Millisecond) + + // Stop beep + err = ioctl(f.Fd(), kiocsound, uintptr(0)) + if err != nil { + return err + } + } + + return nil +} \ No newline at end of file diff --git a/beep_test.go b/beep_test.go new file mode 100644 index 0000000..86c17b7 --- /dev/null +++ b/beep_test.go @@ -0,0 +1,37 @@ +package staros + +import ( + "fmt" + "testing" + "time" +) + +const ( + rat float64 = 1.059463094 //2^(1/12) + C float64 = 493.8833013 * rat + CU = C * rat * rat + D = CU * rat + DU = D * rat + E = DU * rat + F = E * rat + FU = F * rat + G = FU * rat + GU = G * rat + A = GU * rat + AU = A * rat + B = AU * rat +) + +func beepMusic(qual ...float64) { + for _, v := range qual { + fmt.Println(v) + Beep(v, 700) + time.Sleep(time.Millisecond * 1000) + } +} + +func Test_Music(t *testing.T) { + beepMusic(G, D, A, AU, A, G, F, D, DU, D, C, D, AU/2, C, G/2, C, D) + time.Sleep(time.Second * 3) + beepMusic(D,AU,A,G,A,D*2,F*2,G*2,F*2,D*2,D*2,C*2,D*2,DU*2,D*2,AU,A,E,G,FU) +} diff --git a/beep_windows.go b/beep_windows.go new file mode 100644 index 0000000..c61e0be --- /dev/null +++ b/beep_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package staros + +import ( + "syscall" +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 587.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 500 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) error { + if freq == 0 { + freq = DefaultFreq + } else if freq > 32767 { + freq = 32767 + } else if freq < 37 { + freq = DefaultFreq + } + + if duration == 0 { + duration = DefaultDuration + } + + kernel32, _ := syscall.LoadLibrary("kernel32.dll") + beep32, _ := syscall.GetProcAddress(kernel32, "Beep") + + defer syscall.FreeLibrary(kernel32) + + _, _, e := syscall.Syscall(uintptr(beep32), uintptr(2), uintptr(int(freq)), uintptr(duration), 0) + if e != 0 { + return e + } + + return nil +}