// // SourceEdit.jsx // // Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {toast} from 'react-toastify'; import Modal from 'react-modal'; import RestAPI from './Services'; const editCustomStyles = { content : { top: '50%', left: '50%', right: 'auto', bottom: 'auto', marginRight: '-50%', transform: 'translate(-50%, -50%)' } }; const max_packet_size = 1440; //bytes class SourceEdit extends Component { static propTypes = { source: PropTypes.object.isRequired, ticFrameSizeAt1fs: PropTypes.number.isRequired, sampleRate: PropTypes.number.isRequired, applyEdit: PropTypes.func.isRequired, closeEdit: PropTypes.func.isRequired, editIsOpen: PropTypes.bool.isRequired, isEdit: PropTypes.bool.isRequired, editTitle: PropTypes.string.isRequired }; constructor(props) { super(props); this.state = { id: this.props.source.id, enabled: this.props.source.enabled, name: this.props.source.name, nameErr: false, io: this.props.source.io, codec: this.props.source.codec, address: this.props.source.address, addressErr: false, ttl: this.props.source.ttl, ttlErr: false, payloadType: this.props.source.payload_type, payloadTypeErr: false, dscp: this.props.source.dscp, refclkPtpTraceable: this.props.source.refclk_ptp_traceable, channels: this.props.source.map.length, channelsErr: false, map: this.props.source.map, maxSamplesPerPacket: this.getMaxSamplesPerPacket(), maxChannels: this.getMaxChannels(this.props.source.codec, this.getMaxSamplesPerPacket()), audioMap: [] } let v; for (v = 0; v <= (64 - this.state.channels); v += 1) { this.state.audioMap.push(v); } this.onSubmit = this.onSubmit.bind(this); this.onCancel = this.onCancel.bind(this); this.addSource = this.addSource.bind(this); this.onChangeChannels = this.onChangeChannels.bind(this); this.onChangeChannelsMap = this.onChangeChannelsMap.bind(this); this.onChangeMaxSamplesPerPacket = this.onChangeMaxSamplesPerPacket.bind(this); this.onChangeCodec = this.onChangeCodec.bind(this); this.inputIsValid = this.inputIsValid.bind(this); this.checkMaxSamplesPerPacket = this.checkMaxSamplesPerPacket.bind(this); this.getMaxSamplesPerPacket = this.getMaxSamplesPerPacket.bind(this); this.getnFS = this.getnFS.bind(this); this.getPacketDuration = this.getPacketDuration.bind(this); this.getMaxChannels = this.getMaxChannels.bind(this); } componentDidMount() { Modal.setAppElement('body'); } addSource(message) { RestAPI.addSource( this.state.id, this.state.enabled, this.state.name, this.state.io, this.state.maxSamplesPerPacket, this.state.codec, this.state.address ? this.state.address : "", this.state.ttl, this.state.payloadType, this.state.dscp, this.state.refclkPtpTraceable, this.state.map, this.props.isEdit) .then(function(response) { toast.success(message); this.props.applyEdit(); }.bind(this)); } onSubmit() { this.addSource('Source ' + this.state.id + (this.props.isEdit ? ' updated ' : ' added')); } onCancel() { this.props.closeEdit(); } getMaxChannels(codec, samples) { let maxChannels = Math.floor(max_packet_size / (samples * (codec === 'L16' ? 2 : 3))); return maxChannels > 64 ? 64 : maxChannels; } onChangeMaxSamplesPerPacket(e) { let samples = parseInt(e.target.value, 10); let maxChannels = this.getMaxChannels(this.state.codec, samples); this.setState({ maxSamplesPerPacket: samples, maxChannels: maxChannels, channelsErr: this.state.channels > maxChannels }); } onChangeCodec(e) { let codec = e.target.value; let maxChannels = this.getMaxChannels(this.state.codec, this.state.maxSamplesPerPacket); this.setState({ codec: codec, maxChannels: maxChannels, channelsErr: this.state.channels > maxChannels }); } onChangeChannels(e) { if (e.currentTarget.checkValidity()) { let channels = parseInt(e.target.value, 10); let audioMap = [], map = [], v; for (v = 0; v <= (64 - channels); v += 1) { audioMap.push(v); } for (v = 0; v < channels; v++) { map.push(v + this.state.map[0]); } this.setState({ map: map, channels: channels, audioMap: audioMap, channelsErr: false }); } } onChangeChannelsMap(e) { let startChannel = parseInt(e.target.value, 10); let map = [], v; for (v = 0; v < this.state.channels; v++) { map.push(v + startChannel); } this.setState({ map: map }); } getnFS() { switch(this.props.sampleRate) { case 384000: case 352800: return 8; break; case 192000: case 176400: return 4; break; case 96000: case 88200: return 2; break; case 48000: case 44100: default: return 1; } } checkMaxSamplesPerPacket(samples) { return (samples <= (this.props.ticFrameSizeAt1fs * this.getnFS())); } getMaxSamplesPerPacket() { return (this.props.source.max_samples_per_packet > (this.props.ticFrameSizeAt1fs * this.getnFS())) ? (this.props.ticFrameSizeAt1fs * this.getnFS()) : this.props.source.max_samples_per_packet; } getPacketDuration(samples) { let duration = (samples * 1000000) / this.props.sampleRate; if (duration >= 1000) { duration /= 1000; if (duration == Math.round(duration)) return Math.round(duration).toString() + 'ms'; else return (Math.round(duration * 1000) / 1000).toString() + 'ms'; } else return Math.round(duration).toString() + 'μs'; } inputIsValid() { return !this.state.nameErr && !this.state.ttlErr && !this.state.channelsErr && !this.state.payloadTypeErr && !this.state.addressErr; } render() { return (

{this.props.editTitle}

ID this.setState({id: e.target.value})} disabled required/>
this.setState({enabled: e.target.checked})} />
this.setState({name: e.target.value, nameErr: !e.currentTarget.checkValidity()})} required/>
this.setState({address: e.target.value, addressErr: !e.currentTarget.checkValidity()})} optional/>
this.setState({payloadType: e.target.value, payloadTypeErr: !e.currentTarget.checkValidity()})} required/>
this.setState({ttl: e.target.value, ttlErr: !e.currentTarget.checkValidity()})} required/>
this.setState({refclkPtpTraceable: e.target.checked})} />
Audio Channels map

    
); } } export default SourceEdit;